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
33namespace Poco {
34namespace Net {
35
36
37const std::string OAuth10Credentials::SCHEME = "OAuth";
38
39
40OAuth10Credentials::OAuth10Credentials()
41{
42}
43
44
45OAuth10Credentials::OAuth10Credentials(const std::string& consumerKey, const std::string& consumerSecret):
46 _consumerKey(consumerKey),
47 _consumerSecret(consumerSecret)
48{
49}
50
51
52OAuth10Credentials::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
61OAuth10Credentials::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
84OAuth10Credentials::~OAuth10Credentials()
85{
86}
87
88
89void OAuth10Credentials::setConsumerKey(const std::string& consumerKey)
90{
91 _consumerKey = consumerKey;
92}
93
94
95void OAuth10Credentials::setConsumerSecret(const std::string& consumerSecret)
96{
97 _consumerSecret = consumerSecret;
98}
99
100
101void OAuth10Credentials::setToken(const std::string& token)
102{
103 _token = token;
104}
105
106
107void OAuth10Credentials::setTokenSecret(const std::string& tokenSecret)
108{
109 _tokenSecret = tokenSecret;
110}
111
112
113void OAuth10Credentials::setRealm(const std::string& realm)
114{
115 _realm = realm;
116}
117
118
119void OAuth10Credentials::setCallback(const std::string& callback)
120{
121 _callback = callback;
122}
123
124
125void OAuth10Credentials::authenticate(HTTPRequest& request, const Poco::URI& uri, SignatureMethod method)
126{
127 HTMLForm emptyParams;
128 authenticate(request, uri, emptyParams, method);
129}
130
131
132void 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
148bool OAuth10Credentials::verify(const HTTPRequest& request, const Poco::URI& uri)
149{
150 HTMLForm params;
151 return verify(request, uri, params);
152}
153
154
155bool 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
217void OAuth10Credentials::nonceAndTimestampForTesting(const std::string& nonce, const std::string& timestamp)
218{
219 _nonce = nonce;
220 _timestamp = timestamp;
221}
222
223
224void 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
252void 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
290std::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
305std::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
357std::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