1 /***
2  * Copyright (C) Microsoft. All rights reserved.
3  * Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4  *
5  * =+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+=+
6  *
7  * HTTP Library: Oauth 1.0
8  *
9  * For the latest on this and related APIs, please see: https://github.com/Microsoft/cpprestsdk
10  *
11  * =-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-=-
12  ****/
13 #pragma once
14 
15 #ifndef CASA_OAUTH1_H
16 #define CASA_OAUTH1_H
17 
18 #include "cpprest/details/web_utilities.h"
19 #include "cpprest/http_msg.h"
20 
21 namespace web
22 {
23 namespace http
24 {
25 namespace client
26 {
27 // Forward declaration to avoid circular include dependency.
28 class http_client_config;
29 } // namespace client
30 
31 /// oAuth 1.0 library.
32 namespace oauth1
33 {
34 namespace details
35 {
36 class oauth1_handler;
37 
38 // State currently used by oauth1_config to authenticate request.
39 // The state varies for every request (due to timestamp and nonce).
40 // The state also contains extra transmitted protocol parameters during
41 // authorization flow (i.e. 'oauth_callback' or 'oauth_verifier').
42 class oauth1_state
43 {
44 public:
45     oauth1_state(utility::string_t timestamp,
46                  utility::string_t nonce,
47                  utility::string_t extra_key = utility::string_t(),
48                  utility::string_t extra_value = utility::string_t())
m_timestamp(std::move (timestamp))49         : m_timestamp(std::move(timestamp))
50         , m_nonce(std::move(nonce))
51         , m_extra_key(std::move(extra_key))
52         , m_extra_value(std::move(extra_value))
53     {
54     }
55 
timestamp()56     const utility::string_t& timestamp() const { return m_timestamp; }
set_timestamp(utility::string_t timestamp)57     void set_timestamp(utility::string_t timestamp) { m_timestamp = std::move(timestamp); }
58 
nonce()59     const utility::string_t& nonce() const { return m_nonce; }
set_nonce(utility::string_t nonce)60     void set_nonce(utility::string_t nonce) { m_nonce = std::move(nonce); }
61 
extra_key()62     const utility::string_t& extra_key() const { return m_extra_key; }
set_extra_key(utility::string_t key)63     void set_extra_key(utility::string_t key) { m_extra_key = std::move(key); }
64 
extra_value()65     const utility::string_t& extra_value() const { return m_extra_value; }
set_extra_value(utility::string_t value)66     void set_extra_value(utility::string_t value) { m_extra_value = std::move(value); }
67 
68 private:
69     utility::string_t m_timestamp;
70     utility::string_t m_nonce;
71     utility::string_t m_extra_key;
72     utility::string_t m_extra_value;
73 };
74 
75 // Constant strings for OAuth 1.0.
76 typedef utility::string_t oauth1_string;
77 class oauth1_strings
78 {
79 public:
80 #define _OAUTH1_STRINGS
81 #define DAT(a_, b_) _ASYNCRTIMP static const oauth1_string a_;
82 #include "cpprest/details/http_constants.dat"
83 #undef _OAUTH1_STRINGS
84 #undef DAT
85 };
86 
87 } // namespace details
88 
89 /// oAuth functionality is currently in beta.
90 namespace experimental
91 {
92 /// <summary>
93 /// Constant strings for OAuth 1.0 signature methods.
94 /// </summary>
95 typedef utility::string_t oauth1_method;
96 class oauth1_methods
97 {
98 public:
99 #define _OAUTH1_METHODS
100 #define DAT(a, b) _ASYNCRTIMP static const oauth1_method a;
101 #include "cpprest/details/http_constants.dat"
102 #undef _OAUTH1_METHODS
103 #undef DAT
104 };
105 
106 /// <summary>
107 /// Exception type for OAuth 1.0 errors.
108 /// </summary>
109 class oauth1_exception : public std::exception
110 {
111 public:
oauth1_exception(utility::string_t msg)112     oauth1_exception(utility::string_t msg) : m_msg(utility::conversions::to_utf8string(std::move(msg))) {}
~oauth1_exception()113     ~oauth1_exception() CPPREST_NOEXCEPT {}
what()114     const char* what() const CPPREST_NOEXCEPT { return m_msg.c_str(); }
115 
116 private:
117     std::string m_msg;
118 };
119 
120 /// <summary>
121 /// OAuth 1.0 token and associated information.
122 /// </summary>
123 class oauth1_token
124 {
125 public:
126     /// <summary>
127     /// Constructs an initially empty invalid access token.
128     /// </summary>
oauth1_token()129     oauth1_token() {}
130 
131     /// <summary>
132     /// Constructs a OAuth1 token from a given access token and secret.
133     /// </summary>
134     /// <param name="access_token">Access token string.</param>
135     /// <param name="secret">Token secret string.</param>
oauth1_token(utility::string_t access_token,utility::string_t secret)136     oauth1_token(utility::string_t access_token, utility::string_t secret)
137         : m_token(std::move(access_token)), m_secret(std::move(secret))
138     {
139     }
140 
141     /// <summary>
142     /// Get access token validity state.
143     /// If true, token is a valid access token.
144     /// </summary>
145     /// <returns>Access token validity state of the token.</returns>
is_valid_access_token()146     bool is_valid_access_token() const { return !(access_token().empty() || secret().empty()); }
147 
148     /// <summary>
149     /// Get access token.
150     /// </summary>
151     /// <returns>The access token string.</returns>
access_token()152     const utility::string_t& access_token() const { return m_token; }
153 
154     /// <summary>
155     /// Set access token.
156     /// </summary>
157     /// <param name="access_token">Access token string to set.</param>
set_access_token(utility::string_t && access_token)158     void set_access_token(utility::string_t&& access_token) { m_token = std::move(access_token); }
159 
160     /// <summary>
161     /// Set access token.
162     /// </summary>
163     /// <param name="access_token">Access token string to set.</param>
set_access_token(const utility::string_t & access_token)164     void set_access_token(const utility::string_t& access_token) { m_token = access_token; }
165 
166     /// <summary>
167     /// Get token secret.
168     /// </summary>
169     /// <returns>Token secret string.</returns>
secret()170     const utility::string_t& secret() const { return m_secret; }
171 
172     /// <summary>
173     /// Set token secret.
174     /// </summary>
175     /// <param name="secret">Token secret string to set.</param>
set_secret(utility::string_t && secret)176     void set_secret(utility::string_t&& secret) { m_secret = std::move(secret); }
177 
178     /// <summary>
179     /// Set token secret.
180     /// </summary>
181     /// <param name="secret">Token secret string to set.</param>
set_secret(const utility::string_t & secret)182     void set_secret(const utility::string_t& secret) { m_secret = secret; }
183 
184     /// <summary>
185     /// Retrieves any additional parameters.
186     /// </summary>
187     /// <returns>A map containing the additional parameters.</returns>
additional_parameters()188     const std::map<utility::string_t, utility::string_t>& additional_parameters() const
189     {
190         return m_additional_parameters;
191     }
192 
193     /// <summary>
194     /// Sets a specific parameter additional parameter.
195     /// </summary>
196     /// <param name="paramName">Parameter name.</param>
197     /// <param name="paramValue">Parameter value.</param>
set_additional_parameter(utility::string_t && paramName,utility::string_t && paramValue)198     void set_additional_parameter(utility::string_t&& paramName, utility::string_t&& paramValue)
199     {
200         m_additional_parameters[std::move(paramName)] = std::move(paramValue);
201     }
202 
203     /// <summary>
204     /// Sets a specific parameter additional parameter.
205     /// </summary>
206     /// <param name="paramName">Parameter name.</param>
207     /// <param name="paramValue">Parameter value.</param>
set_additional_parameter(const utility::string_t & paramName,const utility::string_t & paramValue)208     void set_additional_parameter(const utility::string_t& paramName, const utility::string_t& paramValue)
209     {
210         m_additional_parameters[paramName] = paramValue;
211     }
212 
213     /// <summary>
214     /// Clears all additional parameters.
215     /// </summary>
clear_additional_parameters()216     void clear_additional_parameters() { m_additional_parameters.clear(); }
217 
218 private:
219     friend class oauth1_config;
220 
221     utility::string_t m_token;
222     utility::string_t m_secret;
223     std::map<utility::string_t, utility::string_t> m_additional_parameters;
224 };
225 
226 /// <summary>
227 /// OAuth 1.0 configuration class.
228 /// </summary>
229 class oauth1_config
230 {
231 public:
232     oauth1_config(utility::string_t consumer_key,
233                   utility::string_t consumer_secret,
234                   utility::string_t temp_endpoint,
235                   utility::string_t auth_endpoint,
236                   utility::string_t token_endpoint,
237                   utility::string_t callback_uri,
238                   oauth1_method method,
239                   utility::string_t realm = utility::string_t())
m_consumer_key(std::move (consumer_key))240         : m_consumer_key(std::move(consumer_key))
241         , m_consumer_secret(std::move(consumer_secret))
242         , m_temp_endpoint(std::move(temp_endpoint))
243         , m_auth_endpoint(std::move(auth_endpoint))
244         , m_token_endpoint(std::move(token_endpoint))
245         , m_callback_uri(std::move(callback_uri))
246         , m_realm(std::move(realm))
247         , m_method(std::move(method))
248         , m_is_authorization_completed(false)
249     {
250     }
251 
252     /// <summary>
253     /// Builds an authorization URI to be loaded in a web browser/view.
254     /// The URI is built with auth_endpoint() as basis.
255     /// The method creates a task for HTTP request to first obtain a
256     /// temporary token. The authorization URI build based on this token.
257     /// </summary>
258     /// <returns>Authorization URI to be loaded in a web browser/view.</returns>
259     _ASYNCRTIMP pplx::task<utility::string_t> build_authorization_uri();
260 
261     /// <summary>
262     /// Fetch an access token based on redirected URI.
263     /// The URI is expected to contain 'oauth_verifier'
264     /// parameter, which is then used to fetch an access token using the
265     /// token_from_verifier() method.
266     /// See: http://tools.ietf.org/html/rfc5849#section-2.2
267     /// The received 'oauth_token' is parsed and verified to match the current token().
268     /// When access token is successfully obtained, set_token() is called, and config is
269     /// ready for use by oauth1_handler.
270     /// </summary>
271     /// <param name="redirected_uri">The URI where web browser/view was redirected after resource owner's
272     /// authorization.</param> <returns>Task that fetches the access token based on redirected URI.</returns>
273     _ASYNCRTIMP pplx::task<void> token_from_redirected_uri(const web::http::uri& redirected_uri);
274 
275     /// <summary>
276     /// Creates a task with HTTP request to fetch an access token from the token endpoint.
277     /// The request exchanges a verifier code to an access token.
278     /// If successful, the resulting token is set as active via set_token().
279     /// See: http://tools.ietf.org/html/rfc5849#section-2.3
280     /// </summary>
281     /// <param name="verifier">Verifier received via redirect upon successful authorization.</param>
282     /// <returns>Task that fetches the access token based on the verifier.</returns>
token_from_verifier(utility::string_t verifier)283     pplx::task<void> token_from_verifier(utility::string_t verifier)
284     {
285         return _request_token(_generate_auth_state(details::oauth1_strings::verifier, std::move(verifier)), false);
286     }
287 
288     /// <summary>
289     /// Creates a task with HTTP request to fetch an access token from the token endpoint.
290     /// If successful, the resulting token is set as active via set_token().
291     /// </summary>
292     /// <returns>Task that fetches the access token based on the verifier.</returns>
refresh_token(const utility::string_t & key)293     pplx::task<void> refresh_token(const utility::string_t& key)
294     {
295         return _request_token(_generate_auth_state(key, m_token.additional_parameters().at(key)), false);
296     }
297 
298     /// <summary>
299     /// Get consumer key used in authorization and authentication.
300     /// </summary>
301     /// <returns>Consumer key string.</returns>
consumer_key()302     const utility::string_t& consumer_key() const { return m_consumer_key; }
303     /// <summary>
304     /// Set consumer key used in authorization and authentication.
305     /// </summary>
306     /// <param name="key">Consumer key string to set.</param>
set_consumer_key(utility::string_t key)307     void set_consumer_key(utility::string_t key) { m_consumer_key = std::move(key); }
308 
309     /// <summary>
310     /// Get consumer secret used in authorization and authentication.
311     /// </summary>
312     /// <returns>Consumer secret string.</returns>
consumer_secret()313     const utility::string_t& consumer_secret() const { return m_consumer_secret; }
314     /// <summary>
315     /// Set consumer secret used in authorization and authentication.
316     /// </summary>
317     /// <param name="secret">Consumer secret string to set.</param>
set_consumer_secret(utility::string_t secret)318     void set_consumer_secret(utility::string_t secret) { m_consumer_secret = std::move(secret); }
319 
320     /// <summary>
321     /// Get temporary token endpoint URI string.
322     /// </summary>
323     /// <returns>Temporary token endpoint URI string.</returns>
temp_endpoint()324     const utility::string_t& temp_endpoint() const { return m_temp_endpoint; }
325     /// <summary>
326     /// Set temporary token endpoint URI string.
327     /// </summary>
328     /// <param name="temp_endpoint">Temporary token endpoint URI string to set.</param>
set_temp_endpoint(utility::string_t temp_endpoint)329     void set_temp_endpoint(utility::string_t temp_endpoint) { m_temp_endpoint = std::move(temp_endpoint); }
330 
331     /// <summary>
332     /// Get authorization endpoint URI string.
333     /// </summary>
334     /// <returns>Authorization endpoint URI string.</returns>
auth_endpoint()335     const utility::string_t& auth_endpoint() const { return m_auth_endpoint; }
336     /// <summary>
337     /// Set authorization endpoint URI string.
338     /// </summary>
339     /// <param name="auth_endpoint">Authorization endpoint URI string to set.</param>
set_auth_endpoint(utility::string_t auth_endpoint)340     void set_auth_endpoint(utility::string_t auth_endpoint) { m_auth_endpoint = std::move(auth_endpoint); }
341 
342     /// <summary>
343     /// Get token endpoint URI string.
344     /// </summary>
345     /// <returns>Token endpoint URI string.</returns>
token_endpoint()346     const utility::string_t& token_endpoint() const { return m_token_endpoint; }
347     /// <summary>
348     /// Set token endpoint URI string.
349     /// </summary>
350     /// <param name="token_endpoint">Token endpoint URI string to set.</param>
set_token_endpoint(utility::string_t token_endpoint)351     void set_token_endpoint(utility::string_t token_endpoint) { m_token_endpoint = std::move(token_endpoint); }
352 
353     /// <summary>
354     /// Get callback URI string.
355     /// </summary>
356     /// <returns>Callback URI string.</returns>
callback_uri()357     const utility::string_t& callback_uri() const { return m_callback_uri; }
358     /// <summary>
359     /// Set callback URI string.
360     /// </summary>
361     /// <param name="callback_uri">Callback URI string to set.</param>
set_callback_uri(utility::string_t callback_uri)362     void set_callback_uri(utility::string_t callback_uri) { m_callback_uri = std::move(callback_uri); }
363 
364     /// <summary>
365     /// Get token.
366     /// </summary>
367     /// <returns>Token.</returns>
368     _ASYNCRTIMP const oauth1_token& token() const;
369 
370     /// <summary>
371     /// Set token.
372     /// </summary>
373     /// <param name="token">Token to set.</param>
set_token(oauth1_token token)374     void set_token(oauth1_token token)
375     {
376         m_token = std::move(token);
377         m_is_authorization_completed = true;
378     }
379 
380     /// <summary>
381     /// Get signature method.
382     /// </summary>
383     /// <returns>Signature method.</returns>
method()384     const oauth1_method& method() const { return m_method; }
385     /// <summary>
386     /// Set signature method.
387     /// </summary>
388     /// <param name="method">Signature method.</param>
set_method(oauth1_method method)389     void set_method(oauth1_method method) { m_method = std::move(method); }
390 
391     /// <summary>
392     /// Get authentication realm.
393     /// </summary>
394     /// <returns>Authentication realm string.</returns>
realm()395     const utility::string_t& realm() const { return m_realm; }
396     /// <summary>
397     /// Set authentication realm.
398     /// </summary>
399     /// <param name="realm">Authentication realm string to set.</param>
set_realm(utility::string_t realm)400     void set_realm(utility::string_t realm) { m_realm = std::move(realm); }
401 
402     /// <summary>
403     /// Returns enabled state of the configuration.
404     /// The oauth1_handler will perform OAuth 1.0 authentication only if
405     /// this method returns true.
406     /// Return value is true if access token is valid (=fetched or manually set)
407     /// and both consumer_key() and consumer_secret() are set (=non-empty).
408     /// </summary>
409     /// <returns>The configuration enabled state.</returns>
is_enabled()410     bool is_enabled() const
411     {
412         return token().is_valid_access_token() && !(consumer_key().empty() || consumer_secret().empty());
413     }
414 
415     // Builds signature base string according to:
416     // http://tools.ietf.org/html/rfc5849#section-3.4.1.1
417     _ASYNCRTIMP utility::string_t _build_signature_base_string(http_request request, details::oauth1_state state) const;
418 
419     // Builds HMAC-SHA1 signature according to:
420     // http://tools.ietf.org/html/rfc5849#section-3.4.2
_build_hmac_sha1_signature(http_request request,details::oauth1_state state)421     utility::string_t _build_hmac_sha1_signature(http_request request, details::oauth1_state state) const
422     {
423         auto text(_build_signature_base_string(std::move(request), std::move(state)));
424         auto digest(_hmac_sha1(_build_key(), std::move(text)));
425         auto signature(utility::conversions::to_base64(std::move(digest)));
426         return signature;
427     }
428 
429     // Builds PLAINTEXT signature according to:
430     // http://tools.ietf.org/html/rfc5849#section-3.4.4
_build_plaintext_signature()431     utility::string_t _build_plaintext_signature() const { return _build_key(); }
432 
_generate_auth_state(utility::string_t extra_key,utility::string_t extra_value)433     details::oauth1_state _generate_auth_state(utility::string_t extra_key, utility::string_t extra_value)
434     {
435         return details::oauth1_state(
436             _generate_timestamp(), _generate_nonce(), std::move(extra_key), std::move(extra_value));
437     }
438 
_generate_auth_state()439     details::oauth1_state _generate_auth_state()
440     {
441         return details::oauth1_state(_generate_timestamp(), _generate_nonce());
442     }
443 
444     /// <summary>
445     /// Gets map of parameters to sign.
446     /// </summary>
447     /// <returns>Map of parameters.</returns>
parameters()448     const std::map<utility::string_t, utility::string_t>& parameters() const { return m_parameters_to_sign; }
449 
450     /// <summary>
451     /// Adds a key value parameter.
452     /// </summary>
453     /// <param name="key">Key as a string value.</param>
454     /// <param name="value">Value as a string value.</param>
add_parameter(const utility::string_t & key,const utility::string_t & value)455     void add_parameter(const utility::string_t& key, const utility::string_t& value)
456     {
457         m_parameters_to_sign[key] = value;
458     }
459 
460     /// <summary>
461     /// Adds a key value parameter.
462     /// </summary>
463     /// <param name="key">Key as a string value.</param>
464     /// <param name="value">Value as a string value.</param>
add_parameter(utility::string_t && key,utility::string_t && value)465     void add_parameter(utility::string_t&& key, utility::string_t&& value)
466     {
467         m_parameters_to_sign[std::move(key)] = std::move(value);
468     }
469 
470     /// <summary>
471     /// Sets entire map or parameters replacing all previously values.
472     /// </summary>
473     /// <param name="parameters">Map of values.</param>
set_parameters(const std::map<utility::string_t,utility::string_t> & parameters)474     void set_parameters(const std::map<utility::string_t, utility::string_t>& parameters)
475     {
476         m_parameters_to_sign.clear();
477         m_parameters_to_sign = parameters;
478     }
479 
480     /// <summary>
481     /// Clears all parameters.
482     /// </summary>
clear_parameters()483     void clear_parameters() { m_parameters_to_sign.clear(); }
484 
485     /// <summary>
486     /// Get the web proxy object
487     /// </summary>
488     /// <returns>A reference to the web proxy object.</returns>
proxy()489     const web_proxy& proxy() const { return m_proxy; }
490 
491     /// <summary>
492     /// Set the web proxy object that will be used by token_from_code and token_from_refresh
493     /// </summary>
494     /// <param name="proxy">A reference to the web proxy object.</param>
set_proxy(const web_proxy & proxy)495     void set_proxy(const web_proxy& proxy) { m_proxy = proxy; }
496 
497 private:
498     friend class web::http::client::http_client_config;
499     friend class web::http::oauth1::details::oauth1_handler;
500 
oauth1_config()501     oauth1_config() : m_is_authorization_completed(false) {}
502 
_generate_nonce()503     utility::string_t _generate_nonce() { return m_nonce_generator.generate(); }
504 
_generate_timestamp()505     static utility::string_t _generate_timestamp()
506     {
507         return utility::conversions::details::to_string_t(utility::datetime::utc_timestamp());
508     }
509 
510     _ASYNCRTIMP static std::vector<unsigned char> __cdecl _hmac_sha1(const utility::string_t& key,
511                                                                      const utility::string_t& data);
512 
513     static utility::string_t _build_base_string_uri(const uri& u);
514 
515     utility::string_t _build_normalized_parameters(web::http::uri u, const details::oauth1_state& state) const;
516 
517     utility::string_t _build_signature(http_request request, details::oauth1_state state) const;
518 
_build_key()519     utility::string_t _build_key() const
520     {
521         return uri::encode_data_string(consumer_secret()) + _XPLATSTR("&") + uri::encode_data_string(m_token.secret());
522     }
523 
_authenticate_request(http_request & req)524     void _authenticate_request(http_request& req) { _authenticate_request(req, _generate_auth_state()); }
525 
526     _ASYNCRTIMP void _authenticate_request(http_request& req, details::oauth1_state state);
527 
528     _ASYNCRTIMP pplx::task<void> _request_token(details::oauth1_state state, bool is_temp_token_request);
529 
530     utility::string_t m_consumer_key;
531     utility::string_t m_consumer_secret;
532     oauth1_token m_token;
533 
534     utility::string_t m_temp_endpoint;
535     utility::string_t m_auth_endpoint;
536     utility::string_t m_token_endpoint;
537     utility::string_t m_callback_uri;
538     utility::string_t m_realm;
539     oauth1_method m_method;
540 
541     std::map<utility::string_t, utility::string_t> m_parameters_to_sign;
542 
543     web::web_proxy m_proxy;
544 
545     utility::nonce_generator m_nonce_generator;
546     bool m_is_authorization_completed;
547 };
548 
549 } // namespace experimental
550 
551 namespace details
552 {
553 class oauth1_handler : public http_pipeline_stage
554 {
555 public:
oauth1_handler(std::shared_ptr<experimental::oauth1_config> cfg)556     oauth1_handler(std::shared_ptr<experimental::oauth1_config> cfg) : m_config(std::move(cfg)) {}
557 
propagate(http_request request)558     virtual pplx::task<http_response> propagate(http_request request) override
559     {
560         if (m_config)
561         {
562             m_config->_authenticate_request(request);
563         }
564         return next_stage()->propagate(request);
565     }
566 
567 private:
568     std::shared_ptr<experimental::oauth1_config> m_config;
569 };
570 
571 } // namespace details
572 } // namespace oauth1
573 } // namespace http
574 } // namespace web
575 
576 #endif
577