userver: /data/code/userver/libraries/s3api/src/s3api/authenticators/utils.cpp Source File
Loading...
Searching...
No Matches
utils.cpp
1#include <userver/s3api/authenticators/utils.hpp>
2
3#include <map>
4#include <optional>
5#include <set>
6#include <sstream>
7
8#include <algorithm>
9
10#include <boost/algorithm/string.hpp>
11
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>
16
17USERVER_NAMESPACE_BEGIN
18
19namespace s3api::authenticators {
20
21namespace {
22
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());
27 return value;
28}
29
30} // namespace
31
32std::string MakeHeaderDate() { return utils::datetime::UtcTimestring(utils::datetime::Now(), "%a, %d %b %Y %T %z"); }
33
34std::string MakeHeaderContentMd5(const std::string& data) {
35 return crypto::hash::weak::Md5(data, crypto::hash::OutputEncoding::kBase64);
36}
37
38std::string MakeStringToSign(
39 const Request& request,
40 const std::string& header_date,
41 const std::optional<std::string>& header_content_md5
42) {
43 std::ostringstream signature;
44
45 signature << ToStringView(request.method) << '\n';
46
47 // md5
48 {
49 if (header_content_md5) {
50 signature << *header_content_md5;
51 }
52
53 signature << '\n';
54 }
55
56 // content type
57 {
58 static const std::string kContentType{"Content-Type"};
59
60 const auto it = request.headers.find(kContentType);
61
62 if (it != request.headers.cend()) {
63 signature << it->second;
64 }
65
66 signature << '\n';
67 }
68
69 // date
70 signature << header_date << '\n';
71
72 // CanonicalizedAmzHeaders
73 {
74 std::vector<std::pair<std::string, std::string>> canonical_headers;
75 canonical_headers.reserve(request.headers.size());
76
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());
80 return utils::StrIcaseEqual{}(header_start, kAmzHeader);
81 });
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;
85 });
86
87 for (const auto& [header, value] : canonical_headers) {
88 signature << header << ':' << RemoveExcessiveSpaces(value) << '\n';
89 }
90 }
91
92 // CanonicalizedResource
93 {
94 // bucket
95 if (!request.bucket.empty()) {
96 signature << '/' + request.bucket;
97 }
98
99 auto actual_subresources = std::set<std::string>{
100 "acl",
101 "lifecycle",
102 "location",
103 "logging",
104 "notification",
105 "partNumber",
106 "policy",
107 "requestPayment",
108 "uploadId",
109 "uploads",
110 "versionId",
111 "versioning",
112 "versions",
113 "website"
114 };
115
116 // query
117 {
118 signature << '/' + request.req.substr(0, request.req.find('?'));
119 }
120
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 == '&'; });
125
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;
129
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);
133 }
134
135 if (actual_subresources.count(subresource) != 0) {
136 subresources.emplace(std::move(subresource), std::move(parameter_value));
137 }
138 }
139
140 bool is_first = true;
141 for (auto& [subresource, value] : subresources) {
142 signature << (is_first ? "?" : "&");
143 is_first = false;
144 if (value) {
145 signature << fmt::format("{}={}", subresource, *value);
146 } else {
147 signature << subresource;
148 }
149 }
150 }
151 }
152
153 return signature.str();
154}
155
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);
158}
159
160std::string MakeHeaderAuthorization(
161 const std::string& string_to_sign,
162 const std::string& access_key,
163 const Secret& secret_key
164) {
165 return "AWS " + access_key + ":" + MakeSignature(string_to_sign, secret_key);
166}
167
168} // namespace s3api::authenticators
169
170USERVER_NAMESPACE_END