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