1#include <userver/s3api/authenticators/utils.hpp>
10#include <boost/algorithm/string.hpp>
12#include <userver/clients/http/request.hpp>
13#include <userver/crypto/hash.hpp>
14#include <userver/utils/datetime_light.hpp>
15#include <userver/utils/str_icase.hpp>
17USERVER_NAMESPACE_BEGIN
23std::string RemoveExcessiveSpaces(std::string value) {
24 std::ranges::replace(value,
'\n',
' ');
25 const auto garbage = std::ranges::unique(value, [](
char a,
char b) {
return a ==
' ' && b ==
' '; });
26 value.erase(garbage.begin(), garbage.end());
34std::string MakeHeaderContentMd5(
const std::string& data) {
38std::string MakeStringToSign(
40 const std::string& header_date,
41 const std::optional<std::string>& header_content_md5
43 std::ostringstream signature;
49 if (header_content_md5) {
50 signature << *header_content_md5;
58 static const std::string kContentType{
"Content-Type"};
60 const auto it = request.headers
.find(kContentType
);
62 if (it != request.headers
.cend()) {
63 signature << it->second;
70 signature << header_date <<
'\n';
74 std::vector<std::pair<std::string, std::string>> canonical_headers;
75 canonical_headers.reserve(request.headers
.size());
77 std::ranges::copy_if(request.headers, std::back_inserter(canonical_headers), [](
const auto& header) {
78 static constexpr std::string_view kAmzHeader =
"x-amz-";
79 auto header_start = std::string_view{header.first}.substr(0, kAmzHeader.size());
82 std::ranges::for_each(canonical_headers, [](
auto& header) { boost::to_lower(header.first); });
83 std::ranges::sort(canonical_headers, [](
const auto& header1,
const auto& header2) {
84 return header1.first < header2.first;
87 for (
const auto& [header, value] : canonical_headers) {
88 signature << header <<
':' << RemoveExcessiveSpaces(value) <<
'\n';
95 if (!request.bucket.empty()) {
96 signature <<
'/' + request.bucket;
99 auto actual_subresources = std::set<std::string>{
118 signature <<
'/' + request.req.substr(0, request.req.find(
'?'));
121 if (
auto pos = request.req.find(
'?'); pos != std::string::npos) {
122 std::vector<std::string> subresources_strings;
123 auto query = request.req.substr(pos + 1);
124 boost::split(subresources_strings, query, [](
char c) {
return c ==
'&'; });
126 std::map<std::string, std::optional<std::string>> subresources;
127 for (
auto&& subresource : subresources_strings) {
128 std::optional<std::string> parameter_value = std::nullopt;
130 if (
auto eq_pos = subresource.find(
'='); eq_pos != std::string::npos) {
131 parameter_value.emplace(subresource.substr(eq_pos + 1));
132 subresource.resize(eq_pos);
135 if (actual_subresources.count(subresource) != 0) {
136 subresources.emplace(std::move(subresource), std::move(parameter_value));
140 bool is_first =
true;
141 for (
auto& [subresource, value] : subresources) {
142 signature << (is_first ?
"?" :
"&");
145 signature << fmt::format(
"{}={}", subresource, *value);
147 signature << subresource;
153 return signature.str();
156std::string MakeSignature(
const std::string& string_to_sign,
const Secret& secret_key) {
157 return crypto::
hash::HmacSha1(secret_key.GetUnderlying(), string_to_sign, crypto::
hash::OutputEncoding::kBase64);
160std::string MakeHeaderAuthorization(
161 const std::string& string_to_sign,
162 const std::string& access_key,
163 const Secret& secret_key
165 return "AWS " + access_key +
":" + MakeSignature(string_to_sign, secret_key);