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 <boost/algorithm/string.hpp>
9
10#include <userver/clients/http/request.hpp>
11#include <userver/crypto/hash.hpp>
12#include <userver/utils/datetime.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() {
55 static constexpr int kHeaderDateLength{64};
56
57 std::string header_date(kHeaderDateLength, '\0');
58
59 auto time = std::chrono::system_clock::to_time_t(utils::datetime::Now());
60 std::tm ptm{};
61 gmtime_r(&time, &ptm);
62 auto result_len = std::strftime(&header_date.front(), header_date.size(), "%a, %d %b %Y %T %z", &ptm);
63
64 if (result_len) {
65 header_date.resize(result_len);
66 } else {
67 std::string kErrorMessage{"MakeHeaderDate strftime fail [exceed maxsize]"};
68 throw std::runtime_error{kErrorMessage};
69 }
70
71 return header_date;
72}
73
74std::string MakeHeaderContentMd5(const std::string& data) {
75 return crypto::hash::weak::Md5(data, crypto::hash::OutputEncoding::kBase64);
76}
77
78std::string MakeStringToSign(
79 const Request& request,
80 const std::string& header_date,
81 const std::optional<std::string>& header_content_md5
82) {
83 std::ostringstream signature;
84
85 signature << HttpMethodToString(request.method) << '\n';
86
87 // md5
88 {
89 if (header_content_md5) {
90 signature << *header_content_md5;
91 }
92
93 signature << '\n';
94 }
95
96 // content type
97 {
98 static const std::string kContentType{"Content-Type"};
99
100 const auto it = request.headers.find(kContentType);
101
102 if (it != request.headers.cend()) {
103 signature << it->second;
104 }
105
106 signature << '\n';
107 }
108
109 // date
110 signature << header_date << '\n';
111
112 // CanonicalizedAmzHeaders
113 {
114 std::vector<std::pair<std::string, std::string>> canonical_headers;
115 canonical_headers.reserve(request.headers.size());
116
117 std::copy_if(
118 request.headers.begin(),
119 request.headers.end(),
120 std::back_inserter(canonical_headers),
121 [](const auto& header) {
122 static const std::string kAmzHeader = "x-amz-";
123 auto header_start = header.first.substr(0, kAmzHeader.size());
124 boost::to_lower(header_start);
125 return header_start == kAmzHeader;
126 }
127 );
128 std::for_each(canonical_headers.begin(), canonical_headers.end(), [](auto& header) {
129 boost::to_lower(header.first);
130 });
131 std::sort(canonical_headers.begin(), canonical_headers.end(), [](const auto& header1, const auto& header2) {
132 return header1.first < header2.first;
133 });
134
135 for (const auto& [header, value] : canonical_headers) {
136 signature << header << ':' << RemoveExcessiveSpaces(value) << '\n';
137 }
138 }
139
140 // CanonicalizedResource
141 {
142 // bucket
143 if (!request.bucket.empty()) {
144 signature << '/' + request.bucket;
145 }
146
147 auto actual_subresources = std::set<std::string>{
148 "acl",
149 "lifecycle",
150 "location",
151 "logging",
152 "notification",
153 "partNumber",
154 "policy",
155 "requestPayment",
156 "uploadId",
157 "uploads",
158 "versionId",
159 "versioning",
160 "versions",
161 "website"};
162
163 // query
164 { signature << '/' + request.req.substr(0, request.req.find('?')); }
165
166 if (auto pos = request.req.find('?'); pos != std::string::npos) {
167 std::vector<std::string> subresources_strings;
168 auto query = request.req.substr(pos + 1);
169 boost::split(subresources_strings, query, [](char c) { return c == '&'; });
170
171 std::map<std::string, std::optional<std::string>> subresources;
172 for (auto&& subresource : subresources_strings) {
173 std::optional<std::string> parameter_value = std::nullopt;
174
175 if (auto eq_pos = subresource.find('='); eq_pos != std::string::npos) {
176 parameter_value.emplace(subresource.substr(eq_pos + 1));
177 subresource.resize(eq_pos);
178 }
179
180 if (actual_subresources.count(subresource) != 0) {
181 subresources.emplace(std::move(subresource), std::move(parameter_value));
182 }
183 }
184
185 bool is_first = true;
186 for (auto& [subresource, value] : subresources) {
187 signature << (is_first ? "?" : "&");
188 is_first = false;
189 if (value) {
190 signature << fmt::format("{}={}", subresource, *value);
191 } else {
192 signature << subresource;
193 }
194 }
195 }
196 }
197
198 return signature.str();
199}
200
201std::string MakeSignature(const std::string& string_to_sign, const Secret& secret_key) {
202 return crypto::hash::HmacSha1(secret_key.GetUnderlying(), string_to_sign, crypto::hash::OutputEncoding::kBase64);
203}
204
205std::string
206MakeHeaderAuthorization(const std::string& string_to_sign, const std::string& access_key, const Secret& secret_key) {
207 return "AWS " + access_key + ":" + MakeSignature(string_to_sign, secret_key);
208}
209
210} // namespace s3api::authenticators
211
212USERVER_NAMESPACE_END