userver: /home/antonyzhilin/arcadia/taxi/uservices/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 <boost/algorithm/string.hpp>
9
10#include <userver/clients/http/request.hpp>
11#include <userver/crypto/hash.hpp>
12#include <userver/utils/datetime_light.hpp>
13
14USERVER_NAMESPACE_BEGIN
15
16namespace s3api::authenticators {
17
18std::string HttpMethodToString(const clients::http::HttpMethod http_method) {
19 std::string http_method_string;
20
21 switch (http_method) {
22 case clients::http::HttpMethod::kDelete:
23 http_method_string = "DELETE";
24 break;
25 case clients::http::HttpMethod::kGet:
26 http_method_string = "GET";
27 break;
28 case clients::http::HttpMethod::kHead:
29 http_method_string = "HEAD";
30 break;
31 case clients::http::HttpMethod::kPost:
32 http_method_string = "POST";
33 break;
34 case clients::http::HttpMethod::kPut:
35 http_method_string = "PUT";
36 break;
37 case clients::http::HttpMethod::kPatch:
38 http_method_string = "PATCH";
39 break;
40 default:
41 throw std::runtime_error("Unknown http method");
42 }
43
44 return http_method_string;
45}
46
47std::string RemoveExcessiveSpaces(std::string value) {
48 std::string result;
49 std::replace(value.begin(), value.end(), '\n', ' ');
50 unique_copy(value.begin(), value.end(), back_inserter(result), [](char a, char b) { return a == ' ' && b == ' '; });
51 return result;
52}
53
54std::string MakeHeaderDate() { return utils::datetime::UtcTimestring(utils::datetime::Now(), "%a, %d %b %Y %T %z"); }
55
56std::string MakeHeaderContentMd5(const std::string& data) {
57 return crypto::hash::weak::Md5(data, crypto::hash::OutputEncoding::kBase64);
58}
59
60std::string MakeStringToSign(
61 const Request& request,
62 const std::string& header_date,
63 const std::optional<std::string>& header_content_md5
64) {
65 std::ostringstream signature;
66
67 signature << HttpMethodToString(request.method) << '\n';
68
69 // md5
70 {
71 if (header_content_md5) {
72 signature << *header_content_md5;
73 }
74
75 signature << '\n';
76 }
77
78 // content type
79 {
80 static const std::string kContentType{"Content-Type"};
81
82 const auto it = request.headers.find(kContentType);
83
84 if (it != request.headers.cend()) {
85 signature << it->second;
86 }
87
88 signature << '\n';
89 }
90
91 // date
92 signature << header_date << '\n';
93
94 // CanonicalizedAmzHeaders
95 {
96 std::vector<std::pair<std::string, std::string>> canonical_headers;
97 canonical_headers.reserve(request.headers.size());
98
99 std::copy_if(
100 request.headers.begin(),
101 request.headers.end(),
102 std::back_inserter(canonical_headers),
103 [](const auto& header) {
104 static const std::string kAmzHeader = "x-amz-";
105 auto header_start = header.first.substr(0, kAmzHeader.size());
106 boost::to_lower(header_start);
107 return header_start == kAmzHeader;
108 }
109 );
110 std::for_each(canonical_headers.begin(), canonical_headers.end(), [](auto& header) {
111 boost::to_lower(header.first);
112 });
113 std::sort(canonical_headers.begin(), canonical_headers.end(), [](const auto& header1, const auto& header2) {
114 return header1.first < header2.first;
115 });
116
117 for (const auto& [header, value] : canonical_headers) {
118 signature << header << ':' << RemoveExcessiveSpaces(value) << '\n';
119 }
120 }
121
122 // CanonicalizedResource
123 {
124 // bucket
125 if (!request.bucket.empty()) {
126 signature << '/' + request.bucket;
127 }
128
129 auto actual_subresources = std::set<std::string>{
130 "acl",
131 "lifecycle",
132 "location",
133 "logging",
134 "notification",
135 "partNumber",
136 "policy",
137 "requestPayment",
138 "uploadId",
139 "uploads",
140 "versionId",
141 "versioning",
142 "versions",
143 "website"};
144
145 // query
146 { signature << '/' + request.req.substr(0, request.req.find('?')); }
147
148 if (auto pos = request.req.find('?'); pos != std::string::npos) {
149 std::vector<std::string> subresources_strings;
150 auto query = request.req.substr(pos + 1);
151 boost::split(subresources_strings, query, [](char c) { return c == '&'; });
152
153 std::map<std::string, std::optional<std::string>> subresources;
154 for (auto&& subresource : subresources_strings) {
155 std::optional<std::string> parameter_value = std::nullopt;
156
157 if (auto eq_pos = subresource.find('='); eq_pos != std::string::npos) {
158 parameter_value.emplace(subresource.substr(eq_pos + 1));
159 subresource.resize(eq_pos);
160 }
161
162 if (actual_subresources.count(subresource) != 0) {
163 subresources.emplace(std::move(subresource), std::move(parameter_value));
164 }
165 }
166
167 bool is_first = true;
168 for (auto& [subresource, value] : subresources) {
169 signature << (is_first ? "?" : "&");
170 is_first = false;
171 if (value) {
172 signature << fmt::format("{}={}", subresource, *value);
173 } else {
174 signature << subresource;
175 }
176 }
177 }
178 }
179
180 return signature.str();
181}
182
183std::string MakeSignature(const std::string& string_to_sign, const Secret& secret_key) {
184 return crypto::hash::HmacSha1(secret_key.GetUnderlying(), string_to_sign, crypto::hash::OutputEncoding::kBase64);
185}
186
187std::string
188MakeHeaderAuthorization(const std::string& string_to_sign, const std::string& access_key, const Secret& secret_key) {
189 return "AWS " + access_key + ":" + MakeSignature(string_to_sign, secret_key);
190}
191
192} // namespace s3api::authenticators
193
194USERVER_NAMESPACE_END