1 | // |
2 | // OAuth10Credentials.cpp |
3 | // |
4 | // Library: Net |
5 | // Package: OAuth |
6 | // Module: OAuth10Credentials |
7 | // |
8 | // Copyright (c) 2014, Applied Informatics Software Engineering GmbH. |
9 | // and Contributors. |
10 | // |
11 | // SPDX-License-Identifier: BSL-1.0 |
12 | // |
13 | |
14 | |
15 | #include "Poco/Net/OAuth10Credentials.h" |
16 | #include "Poco/Net/HTTPRequest.h" |
17 | #include "Poco/Net/HTMLForm.h" |
18 | #include "Poco/Net/NetException.h" |
19 | #include "Poco/Net/HTTPAuthenticationParams.h" |
20 | #include "Poco/SHA1Engine.h" |
21 | #include "Poco/HMACEngine.h" |
22 | #include "Poco/Base64Encoder.h" |
23 | #include "Poco/RandomStream.h" |
24 | #include "Poco/Timestamp.h" |
25 | #include "Poco/NumberParser.h" |
26 | #include "Poco/NumberFormatter.h" |
27 | #include "Poco/Format.h" |
28 | #include "Poco/String.h" |
29 | #include <map> |
30 | #include <sstream> |
31 | |
32 | |
33 | namespace Poco { |
34 | namespace Net { |
35 | |
36 | |
37 | const std::string OAuth10Credentials::SCHEME = "OAuth" ; |
38 | |
39 | |
40 | OAuth10Credentials::OAuth10Credentials() |
41 | { |
42 | } |
43 | |
44 | |
45 | OAuth10Credentials::OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret): |
46 | _consumerKey(consumerKey), |
47 | _consumerSecret(consumerSecret) |
48 | { |
49 | } |
50 | |
51 | |
52 | OAuth10Credentials::OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret, const std::string& token, const std::string& tokenSecret): |
53 | _consumerKey(consumerKey), |
54 | _consumerSecret(consumerSecret), |
55 | _token(token), |
56 | _tokenSecret(tokenSecret) |
57 | { |
58 | } |
59 | |
60 | |
61 | OAuth10Credentials::OAuth10Credentials(const Poco::Net::HTTPRequest& request) |
62 | { |
63 | if (request.hasCredentials()) |
64 | { |
65 | std::string authScheme; |
66 | std::string authParams; |
67 | request.getCredentials(authScheme, authParams); |
68 | if (icompare(authScheme, SCHEME) == 0) |
69 | { |
70 | HTTPAuthenticationParams params(authParams); |
71 | std::string consumerKey = params.get("oauth_consumer_key" , "" ); |
72 | URI::decode(consumerKey, _consumerKey); |
73 | std::string token = params.get("oauth_token" , "" ); |
74 | URI::decode(token, _token); |
75 | std::string callback = params.get("oauth_callback" , "" ); |
76 | URI::decode(callback, _callback); |
77 | } |
78 | else throw NotAuthenticatedException("No OAuth credentials in Authorization header" , authScheme); |
79 | } |
80 | else throw NotAuthenticatedException("No Authorization header found" ); |
81 | } |
82 | |
83 | |
84 | OAuth10Credentials::~OAuth10Credentials() |
85 | { |
86 | } |
87 | |
88 | |
89 | void OAuth10Credentials::setConsumerKey(const std::string& consumerKey) |
90 | { |
91 | _consumerKey = consumerKey; |
92 | } |
93 | |
94 | |
95 | void OAuth10Credentials::setConsumerSecret(const std::string& consumerSecret) |
96 | { |
97 | _consumerSecret = consumerSecret; |
98 | } |
99 | |
100 | |
101 | void OAuth10Credentials::setToken(const std::string& token) |
102 | { |
103 | _token = token; |
104 | } |
105 | |
106 | |
107 | void OAuth10Credentials::setTokenSecret(const std::string& tokenSecret) |
108 | { |
109 | _tokenSecret = tokenSecret; |
110 | } |
111 | |
112 | |
113 | void OAuth10Credentials::setRealm(const std::string& realm) |
114 | { |
115 | _realm = realm; |
116 | } |
117 | |
118 | |
119 | void OAuth10Credentials::setCallback(const std::string& callback) |
120 | { |
121 | _callback = callback; |
122 | } |
123 | |
124 | |
125 | void OAuth10Credentials::authenticate(HTTPRequest& request, const Poco::URI& uri, SignatureMethod method) |
126 | { |
127 | HTMLForm emptyParams; |
128 | authenticate(request, uri, emptyParams, method); |
129 | } |
130 | |
131 | |
132 | void OAuth10Credentials::authenticate(HTTPRequest& request, const Poco::URI& uri, const Poco::Net::HTMLForm& params, SignatureMethod method) |
133 | { |
134 | if (method == SIGN_PLAINTEXT) |
135 | { |
136 | signPlaintext(request); |
137 | } |
138 | else |
139 | { |
140 | URI uriWithoutQuery(uri); |
141 | uriWithoutQuery.setQuery("" ); |
142 | uriWithoutQuery.setFragment("" ); |
143 | signHMACSHA1(request, uriWithoutQuery.toString(), params); |
144 | } |
145 | } |
146 | |
147 | |
148 | bool OAuth10Credentials::verify(const HTTPRequest& request, const Poco::URI& uri) |
149 | { |
150 | HTMLForm params; |
151 | return verify(request, uri, params); |
152 | } |
153 | |
154 | |
155 | bool OAuth10Credentials::verify(const HTTPRequest& request, const Poco::URI& uri, const Poco::Net::HTMLForm& params) |
156 | { |
157 | if (request.hasCredentials()) |
158 | { |
159 | std::string authScheme; |
160 | std::string authParams; |
161 | request.getCredentials(authScheme, authParams); |
162 | if (icompare(authScheme, SCHEME) == 0) |
163 | { |
164 | HTTPAuthenticationParams oauthParams(authParams); |
165 | |
166 | std::string version = oauthParams.get("oauth_version" , "1.0" ); |
167 | if (version != "1.0" ) throw NotAuthenticatedException("Unsupported OAuth version" , version); |
168 | |
169 | _consumerKey.clear(); |
170 | std::string consumerKey = oauthParams.get("oauth_consumer_key" , "" ); |
171 | URI::decode(consumerKey, _consumerKey); |
172 | |
173 | _token.clear(); |
174 | std::string token = oauthParams.get("oauth_token" , "" ); |
175 | URI::decode(token, _token); |
176 | |
177 | _callback.clear(); |
178 | std::string callback = oauthParams.get("oauth_callback" , "" ); |
179 | URI::decode(callback, _callback); |
180 | |
181 | std::string nonceEnc = oauthParams.get("oauth_nonce" , "" ); |
182 | std::string nonce; |
183 | URI::decode(nonceEnc, nonce); |
184 | |
185 | std::string timestamp = oauthParams.get("oauth_timestamp" , "" ); |
186 | |
187 | std::string method = oauthParams.get("oauth_signature_method" , "" ); |
188 | |
189 | std::string signatureEnc = oauthParams.get("oauth_signature" , "" ); |
190 | std::string signature; |
191 | URI::decode(signatureEnc, signature); |
192 | |
193 | std::string refSignature; |
194 | if (icompare(method, "PLAINTEXT" ) == 0) |
195 | { |
196 | refSignature = percentEncode(_consumerSecret); |
197 | refSignature += '&'; |
198 | refSignature += percentEncode(_tokenSecret); |
199 | } |
200 | else if (icompare(method, "HMAC-SHA1" ) == 0) |
201 | { |
202 | URI uriWithoutQuery(uri); |
203 | uriWithoutQuery.setQuery("" ); |
204 | uriWithoutQuery.setFragment("" ); |
205 | refSignature = createSignature(request, uriWithoutQuery.toString(), params, nonce, timestamp); |
206 | } |
207 | else throw NotAuthenticatedException("Unsupported OAuth signature method" , method); |
208 | |
209 | return refSignature == signature; |
210 | } |
211 | else throw NotAuthenticatedException("No OAuth credentials found in Authorization header" ); |
212 | } |
213 | else throw NotAuthenticatedException("No Authorization header found" ); |
214 | } |
215 | |
216 | |
217 | void OAuth10Credentials::nonceAndTimestampForTesting(const std::string& nonce, const std::string& timestamp) |
218 | { |
219 | _nonce = nonce; |
220 | _timestamp = timestamp; |
221 | } |
222 | |
223 | |
224 | void OAuth10Credentials::signPlaintext(Poco::Net::HTTPRequest& request) const |
225 | { |
226 | std::string signature(percentEncode(_consumerSecret)); |
227 | signature += '&'; |
228 | signature += percentEncode(_tokenSecret); |
229 | |
230 | std::string authorization(SCHEME); |
231 | if (!_realm.empty()) |
232 | { |
233 | Poco::format(authorization, " realm=\"%s\"," , _realm); |
234 | } |
235 | Poco::format(authorization, " oauth_consumer_key=\"%s\"" , percentEncode(_consumerKey)); |
236 | Poco::format(authorization, ", oauth_signature=\"%s\"" , percentEncode(signature)); |
237 | authorization += ", oauth_signature_method=\"PLAINTEXT\"" ; |
238 | if (!_token.empty()) |
239 | { |
240 | Poco::format(authorization, ", oauth_token=\"%s\"" , percentEncode(_token)); |
241 | } |
242 | if (!_callback.empty()) |
243 | { |
244 | Poco::format(authorization, ", oauth_callback=\"%s\"" , percentEncode(_callback)); |
245 | } |
246 | authorization += ", oauth_version=\"1.0\"" ; |
247 | |
248 | request.set(HTTPRequest::AUTHORIZATION, authorization); |
249 | } |
250 | |
251 | |
252 | void OAuth10Credentials::signHMACSHA1(Poco::Net::HTTPRequest& request, const std::string& uri, const Poco::Net::HTMLForm& params) const |
253 | { |
254 | std::string nonce(_nonce); |
255 | if (nonce.empty()) |
256 | { |
257 | nonce = createNonce(); |
258 | } |
259 | std::string timestamp(_timestamp); |
260 | if (timestamp.empty()) |
261 | { |
262 | timestamp = Poco::NumberFormatter::format(static_cast<Poco::UInt64>(Poco::Timestamp().epochTime())); |
263 | } |
264 | std::string signature(createSignature(request, uri, params, nonce, timestamp)); |
265 | |
266 | std::string authorization(SCHEME); |
267 | if (!_realm.empty()) |
268 | { |
269 | Poco::format(authorization, " realm=\"%s\"," , _realm); |
270 | } |
271 | Poco::format(authorization, " oauth_consumer_key=\"%s\"" , percentEncode(_consumerKey)); |
272 | Poco::format(authorization, ", oauth_nonce=\"%s\"" , percentEncode(nonce)); |
273 | Poco::format(authorization, ", oauth_signature=\"%s\"" , percentEncode(signature)); |
274 | authorization += ", oauth_signature_method=\"HMAC-SHA1\"" ; |
275 | Poco::format(authorization, ", oauth_timestamp=\"%s\"" , timestamp); |
276 | if (!_token.empty()) |
277 | { |
278 | Poco::format(authorization, ", oauth_token=\"%s\"" , percentEncode(_token)); |
279 | } |
280 | if (!_callback.empty()) |
281 | { |
282 | Poco::format(authorization, ", oauth_callback=\"%s\"" , percentEncode(_callback)); |
283 | } |
284 | authorization += ", oauth_version=\"1.0\"" ; |
285 | |
286 | request.set(HTTPRequest::AUTHORIZATION, authorization); |
287 | } |
288 | |
289 | |
290 | std::string OAuth10Credentials::createNonce() const |
291 | { |
292 | std::ostringstream base64Nonce; |
293 | Poco::Base64Encoder base64Encoder(base64Nonce); |
294 | Poco::RandomInputStream randomStream; |
295 | for (int i = 0; i < 32; i++) |
296 | { |
297 | base64Encoder.put(randomStream.get()); |
298 | } |
299 | base64Encoder.close(); |
300 | std::string nonce = base64Nonce.str(); |
301 | return Poco::translate(nonce, "+/=" , "" ); |
302 | } |
303 | |
304 | |
305 | std::string OAuth10Credentials::createSignature(const Poco::Net::HTTPRequest& request, const std::string& uri, const Poco::Net::HTMLForm& params, const std::string& nonce, const std::string& timestamp) const |
306 | { |
307 | std::map<std::string, std::string> paramsMap; |
308 | paramsMap["oauth_version" ] = "1.0" ; |
309 | paramsMap["oauth_consumer_key" ] = percentEncode(_consumerKey); |
310 | paramsMap["oauth_nonce" ] = percentEncode(nonce); |
311 | paramsMap["oauth_signature_method" ] = "HMAC-SHA1" ; |
312 | paramsMap["oauth_timestamp" ] = timestamp; |
313 | if (!_token.empty()) |
314 | { |
315 | paramsMap["oauth_token" ] = percentEncode(_token); |
316 | } |
317 | if (!_callback.empty()) |
318 | { |
319 | paramsMap["oauth_callback" ] = percentEncode(_callback); |
320 | } |
321 | for (Poco::Net::HTMLForm::ConstIterator it = params.begin(); it != params.end(); ++it) |
322 | { |
323 | paramsMap[percentEncode(it->first)] = percentEncode(it->second); |
324 | } |
325 | |
326 | std::string paramsString; |
327 | for (std::map<std::string, std::string>::const_iterator it = paramsMap.begin(); it != paramsMap.end(); ++it) |
328 | { |
329 | if (it != paramsMap.begin()) paramsString += '&'; |
330 | paramsString += it->first; |
331 | paramsString += "=" ; |
332 | paramsString += it->second; |
333 | } |
334 | |
335 | std::string signatureBase = request.getMethod(); |
336 | signatureBase += '&'; |
337 | signatureBase += percentEncode(uri); |
338 | signatureBase += '&'; |
339 | signatureBase += percentEncode(paramsString); |
340 | |
341 | std::string signingKey; |
342 | signingKey += percentEncode(_consumerSecret); |
343 | signingKey += '&'; |
344 | signingKey += percentEncode(_tokenSecret); |
345 | |
346 | Poco::HMACEngine<Poco::SHA1Engine> hmacEngine(signingKey); |
347 | hmacEngine.update(signatureBase); |
348 | Poco::DigestEngine::Digest digest = hmacEngine.digest(); |
349 | std::ostringstream digestBase64; |
350 | Poco::Base64Encoder base64Encoder(digestBase64); |
351 | base64Encoder.write(reinterpret_cast<char*>(&digest[0]), digest.size()); |
352 | base64Encoder.close(); |
353 | return digestBase64.str(); |
354 | } |
355 | |
356 | |
357 | std::string OAuth10Credentials::percentEncode(const std::string& str) |
358 | { |
359 | std::string encoded; |
360 | Poco::URI::encode(str, "!?#/'\",;:$&()[]*+=@" , encoded); |
361 | return encoded; |
362 | } |
363 | |
364 | |
365 | } } // namespace Poco::Net |
366 | |