PageRenderTime 21ms CodeModel.GetById 1ms RepoModel.GetById 1ms app.codeStats 0ms

/mordor/http/oauth.cpp

http://github.com/mozy/mordor
C++ | 400 lines | 353 code | 34 blank | 13 comment | 89 complexity | 048564e3f00cced2a565df42f74135dc MD5 | raw file
Possible License(s): BSD-3-Clause
  1. // Copyright (c) 2009 - Mozy, Inc.
  2. #include "oauth.h"
  3. #include "client.h"
  4. #include "mordor/date_time.h"
  5. #include "mordor/streams/stream.h"
  6. namespace Mordor {
  7. namespace HTTP {
  8. namespace OAuth {
  9. static void writeBody(ClientRequest::ptr request, const std::string &body)
  10. {
  11. request->requestStream()->write(body.c_str(), body.size());
  12. request->requestStream()->close();
  13. }
  14. std::pair<std::string, std::string>
  15. extractCredentials(ClientRequest::ptr request)
  16. {
  17. URI::QueryString responseParams = request->responseStream();
  18. std::pair<std::string, std::string> result;
  19. std::pair<URI::QueryString::iterator, URI::QueryString::iterator> its =
  20. responseParams.equal_range("oauth_token");
  21. if (its.first == responseParams.end() || its.first->second.empty())
  22. MORDOR_THROW_EXCEPTION(InvalidResponseException("Missing oauth_token in response",
  23. request));
  24. result.first = its.first->second;
  25. ++its.first;
  26. if (its.first != its.second)
  27. MORDOR_THROW_EXCEPTION(InvalidResponseException("Duplicate oauth_token in response",
  28. request));
  29. its = responseParams.equal_range("oauth_token_secret");
  30. if (its.first == responseParams.end() || its.first->second.empty())
  31. MORDOR_THROW_EXCEPTION(InvalidResponseException("Missing oauth_token_secret in response",
  32. request));
  33. result.second = its.first->second;
  34. ++its.first;
  35. if (its.first != its.second)
  36. MORDOR_THROW_EXCEPTION(InvalidResponseException("Duplicate oauth_token_secret in response",
  37. request));
  38. return result;
  39. }
  40. std::pair<std::string, std::string>
  41. getTemporaryCredentials(RequestBroker::ptr requestBroker, const URI &uri,
  42. const std::string &method, const std::string &signatureMethod,
  43. const std::pair<std::string, std::string> &clientCredentials,
  44. const URI &callbackUri)
  45. {
  46. MORDOR_ASSERT(requestBroker);
  47. MORDOR_ASSERT(uri.isDefined());
  48. MORDOR_ASSERT(method == GET || method == POST);
  49. MORDOR_ASSERT(signatureMethod == "PLAINTEXT" || signatureMethod == "HMAC-SHA1");
  50. MORDOR_ASSERT(!clientCredentials.first.empty());
  51. MORDOR_ASSERT(!clientCredentials.second.empty());
  52. URI::QueryString oauthParameters;
  53. oauthParameters.insert(std::make_pair("oauth_consumer_key", clientCredentials.first));
  54. oauthParameters.insert(std::make_pair("oauth_version", "1.0"));
  55. if (!callbackUri.isDefined())
  56. oauthParameters.insert(std::make_pair("oauth_callback", "oob"));
  57. else
  58. oauthParameters.insert(std::make_pair("oauth_callback", callbackUri.toString()));
  59. nonceAndTimestamp(oauthParameters);
  60. sign(uri, method, signatureMethod, clientCredentials.second, std::string(),
  61. oauthParameters);
  62. Request requestHeaders;
  63. requestHeaders.requestLine.method = method;
  64. requestHeaders.requestLine.uri = uri;
  65. std::string body;
  66. if (method == GET) {
  67. // Add parameters that are part of the request token URI
  68. URI::QueryString qsFromUri = uri.queryString();
  69. oauthParameters.insert(qsFromUri.begin(), qsFromUri.end());
  70. requestHeaders.requestLine.uri.query(oauthParameters);
  71. } else {
  72. body = oauthParameters.toString();
  73. requestHeaders.entity.contentType.type = "application";
  74. requestHeaders.entity.contentType.subtype = "x-www-form-urlencoded";
  75. requestHeaders.entity.contentLength = body.size();
  76. }
  77. boost::function<void (ClientRequest::ptr)> bodyDg;
  78. if (!body.empty())
  79. bodyDg = boost::bind(&writeBody, _1, boost::cref(body));
  80. ClientRequest::ptr request;
  81. try {
  82. request = requestBroker->request(requestHeaders, false, bodyDg);
  83. } catch (...) {
  84. throw;
  85. }
  86. if (request->response().status.status != OK)
  87. MORDOR_THROW_EXCEPTION(InvalidResponseException(request));
  88. return extractCredentials(request);
  89. }
  90. std::pair<std::string, std::string>
  91. getTokenCredentials(RequestBroker::ptr requestBroker, const URI &uri,
  92. const std::string &method, const std::string &signatureMethod,
  93. const std::pair<std::string, std::string> &clientCredentials,
  94. const std::pair<std::string, std::string> &temporaryCredentials,
  95. const std::string &verifier)
  96. {
  97. MORDOR_ASSERT(requestBroker);
  98. MORDOR_ASSERT(uri.isDefined());
  99. MORDOR_ASSERT(method == GET || method == POST);
  100. MORDOR_ASSERT(signatureMethod == "PLAINTEXT" || signatureMethod == "HMAC-SHA1");
  101. MORDOR_ASSERT(!clientCredentials.first.empty());
  102. MORDOR_ASSERT(!clientCredentials.second.empty());
  103. MORDOR_ASSERT(!temporaryCredentials.first.empty());
  104. MORDOR_ASSERT(!temporaryCredentials.second.empty());
  105. URI::QueryString oauthParameters;
  106. oauthParameters.insert(std::make_pair("oauth_consumer_key", clientCredentials.first));
  107. oauthParameters.insert(std::make_pair("oauth_token", temporaryCredentials.first));
  108. oauthParameters.insert(std::make_pair("oauth_verifier", verifier));
  109. oauthParameters.insert(std::make_pair("oauth_version", "1.0"));
  110. nonceAndTimestamp(oauthParameters);
  111. sign(uri, method, signatureMethod, clientCredentials.second,
  112. temporaryCredentials.second, oauthParameters);
  113. Request requestHeaders;
  114. requestHeaders.requestLine.method = method;
  115. requestHeaders.requestLine.uri = uri;
  116. std::string body;
  117. if (method == GET) {
  118. // Add parameters that are part of the request token URI
  119. URI::QueryString qsFromUri = uri.queryString();
  120. oauthParameters.insert(qsFromUri.begin(), qsFromUri.end());
  121. requestHeaders.requestLine.uri.query(oauthParameters);
  122. } else {
  123. body = oauthParameters.toString();
  124. requestHeaders.entity.contentType.type = "application";
  125. requestHeaders.entity.contentType.subtype = "x-www-form-urlencoded";
  126. requestHeaders.entity.contentLength = body.size();
  127. }
  128. boost::function<void (ClientRequest::ptr)> bodyDg;
  129. if (!body.empty())
  130. bodyDg = boost::bind(&writeBody, _1, boost::cref(body));
  131. ClientRequest::ptr request;
  132. try {
  133. request = requestBroker->request(requestHeaders, false, bodyDg);
  134. } catch (...) {
  135. throw;
  136. }
  137. if (request->response().status.status != OK)
  138. MORDOR_THROW_EXCEPTION(InvalidResponseException(request));
  139. return extractCredentials(request);
  140. }
  141. void
  142. authorize(Request &nextRequest, const std::string &signatureMethod,
  143. const std::pair<std::string, std::string> &clientCredentials,
  144. const std::pair<std::string, std::string> &tokenCredentials,
  145. const std::string &realm, const std::string &scheme)
  146. {
  147. MORDOR_ASSERT(signatureMethod == "PLAINTEXT" || signatureMethod == "HMAC-SHA1");
  148. MORDOR_ASSERT(!clientCredentials.first.empty());
  149. MORDOR_ASSERT(!clientCredentials.second.empty());
  150. MORDOR_ASSERT(!tokenCredentials.first.empty());
  151. MORDOR_ASSERT(!tokenCredentials.second.empty());
  152. AuthParams &authorization = nextRequest.request.authorization;
  153. authorization.scheme = "OAuth";
  154. authorization.param.clear();
  155. authorization.parameters.clear();
  156. authorization.parameters["oauth_consumer_key"] = clientCredentials.first;
  157. authorization.parameters["oauth_token"] = tokenCredentials.first;
  158. authorization.parameters["oauth_version"] = "1.0";
  159. nonceAndTimestamp(authorization.parameters);
  160. URI uri = nextRequest.requestLine.uri;
  161. if (!uri.authority.hostDefined()) {
  162. MORDOR_ASSERT(!scheme.empty());
  163. std::string fullUri = scheme + "://" + nextRequest.request.host +
  164. uri.toString();
  165. uri = fullUri;
  166. }
  167. sign(uri, nextRequest.requestLine.method, signatureMethod,
  168. clientCredentials.second, tokenCredentials.second,
  169. authorization.parameters);
  170. if (!realm.empty())
  171. authorization.parameters["realm"] = realm;
  172. // OAuth is stupid, and doesn't trust quoted-string in the Authorization
  173. // header, so we have to use their encoding method (which is really just
  174. // URI encoding, only encoding characters not in the unreserved character
  175. // set).
  176. // Note that technically Mordor breaks the OAuth spec because when it
  177. // serializes, it will only add quotes when necessary, whereas OAuth
  178. // (again, very naively) requires quotes on all values.
  179. for (StringMap::iterator it = authorization.parameters.begin();
  180. it != authorization.parameters.end();
  181. ++it) {
  182. it->second = URI::encode(it->second);
  183. }
  184. }
  185. template <class T>
  186. void nonceAndTimestamp(T &oauthParameters)
  187. {
  188. static const char *allowedChars =
  189. "0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ";
  190. std::string nonce;
  191. nonce.resize(40);
  192. for (size_t i = 0; i < 40; ++i) {
  193. nonce[i] = allowedChars[rand() % 36];
  194. }
  195. typename T::iterator it = oauthParameters.find("oauth_timestamp");
  196. if (it != oauthParameters.end())
  197. oauthParameters.erase(it);
  198. it = oauthParameters.find("oauth_nonce");
  199. if (it != oauthParameters.end())
  200. oauthParameters.erase(it);
  201. oauthParameters.insert(std::make_pair("oauth_timestamp",
  202. boost::lexical_cast<std::string>(toTimeT(
  203. boost::posix_time::second_clock::universal_time()))));
  204. oauthParameters.insert(std::make_pair("oauth_nonce", nonce));
  205. }
  206. template void nonceAndTimestamp<StringMap>(StringMap &);
  207. template void nonceAndTimestamp<URI::QueryString>(URI::QueryString &);
  208. template <class T>
  209. std::string
  210. generateSignature(const URI &uri, const std::string &method,
  211. const std::string &clientSecret, const std::string &tokenSecret,
  212. const T &oauthParameters, const URI::QueryString &postParameters)
  213. {
  214. MORDOR_ASSERT(oauthParameters.find("oauth_signature_method") != oauthParameters.end());
  215. typename T::const_iterator it;
  216. std::ostringstream os;
  217. URI requestUri(uri);
  218. requestUri.queryDefined(false);
  219. requestUri.fragmentDefined(false);
  220. requestUri.normalize();
  221. os << method << '&' << URI::encode(requestUri.toString());
  222. std::multiset<std::pair<std::string, std::string> > combined;
  223. std::multiset<std::pair<std::string, std::string> >::iterator
  224. combinedIt;
  225. for (it = oauthParameters.begin(); it != oauthParameters.end(); ++it)
  226. if (stricmp(it->first.c_str(), "realm") != 0 &&
  227. stricmp(it->first.c_str(), "oauth_signature") != 0)
  228. combined.insert(*it);
  229. URI::QueryString::const_iterator it2;
  230. for (it2 = postParameters.begin(); it2 != postParameters.end(); ++it2)
  231. combined.insert(*it2);
  232. if (uri.queryDefined()) {
  233. URI::QueryString queryParams = uri.queryString();
  234. for (it2 = queryParams.begin(); it2 != queryParams.end(); ++it2)
  235. combined.insert(*it2);
  236. }
  237. os << '&';
  238. std::string signatureBaseString = os.str();
  239. os.str("");
  240. bool first = true;
  241. for (combinedIt = combined.begin();
  242. combinedIt != combined.end();
  243. ++combinedIt) {
  244. if (!first)
  245. os << '&';
  246. first = false;
  247. os << URI::encode(combinedIt->first)
  248. << '=' << URI::encode(combinedIt->second);
  249. }
  250. signatureBaseString.append(URI::encode(os.str()));
  251. std::string secrets = URI::encode(clientSecret);
  252. secrets.append(1, '&');
  253. secrets.append(URI::encode(tokenSecret));
  254. it = oauthParameters.find("oauth_signature_method");
  255. const std::string &signatureMethod = it->second;
  256. if (stricmp(signatureMethod.c_str(), "HMAC-SHA1") == 0) {
  257. return base64encode(hmacSha1(signatureBaseString, secrets));
  258. } else if (stricmp(signatureMethod.c_str(), "PLAINTEXT") == 0) {
  259. return secrets;
  260. } else {
  261. MORDOR_NOTREACHED();
  262. }
  263. }
  264. template <class T>
  265. void sign(const URI &uri, const std::string &method,
  266. const std::string &signatureMethod, const std::string &clientSecret,
  267. const std::string &tokenSecret, T &oauthParameters,
  268. const URI::QueryString &postParameters)
  269. {
  270. std::pair<typename T::iterator, typename T::iterator> its =
  271. oauthParameters.equal_range("oauth_signature_method");
  272. if (its.first == oauthParameters.end() ||
  273. boost::next(its.first) != its.second ||
  274. its.first->second != signatureMethod) {
  275. oauthParameters.erase(its.first, its.second);
  276. oauthParameters.insert(std::make_pair("oauth_signature_method",
  277. signatureMethod));
  278. }
  279. its = oauthParameters.equal_range("oauth_signature");
  280. oauthParameters.erase(its.first, its.second);
  281. oauthParameters.insert(std::make_pair("oauth_signature",
  282. generateSignature(uri, method, clientSecret, tokenSecret,
  283. oauthParameters, postParameters)));
  284. }
  285. template void sign<StringMap>(const URI &, const std::string &,
  286. const std::string &, const std::string &, const std::string &, StringMap &,
  287. const URI::QueryString &);
  288. template void sign<URI::QueryString>(const URI &, const std::string &,
  289. const std::string &, const std::string &, const std::string &,
  290. URI::QueryString &, const URI::QueryString &);
  291. template <class T>
  292. bool validate(const URI &uri, const std::string &method,
  293. const std::string &clientSecret, const std::string &tokenSecret,
  294. const T &oauthParameters,
  295. const URI::QueryString &postParameters)
  296. {
  297. std::pair<typename T::const_iterator, typename T::const_iterator> its =
  298. oauthParameters.equal_range("oauth_signature_method");
  299. // Not exactly one signature method
  300. if (its.first == oauthParameters.end() ||
  301. boost::next(its.first) != its.second)
  302. return false;
  303. // Unsupported signature method
  304. if (stricmp(its.first->second.c_str(), "PLAINTEXT") != 0 &&
  305. stricmp(its.first->second.c_str(), "HMAC-SHA1") != 0)
  306. return false;
  307. its = oauthParameters.equal_range("oauth_signature");
  308. // Not exactly one signature
  309. if (its.first == oauthParameters.end() ||
  310. boost::next(its.first) != its.second)
  311. return false;
  312. return its.first->second == generateSignature(uri, method,
  313. clientSecret, tokenSecret, oauthParameters, postParameters);
  314. }
  315. template bool validate<StringMap>(const URI &, const std::string &,
  316. const std::string &, const std::string &, const StringMap &,
  317. const URI::QueryString &);
  318. template bool validate<URI::QueryString>(const URI &, const std::string &,
  319. const std::string &, const std::string &, const URI::QueryString &,
  320. const URI::QueryString &);
  321. static void authorizeDg(ClientRequest::ptr request,
  322. const std::string &signatureMethod,
  323. const std::pair<std::string, std::string> &clientCredentials,
  324. const std::pair<std::string, std::string> &tokenCredentials,
  325. const std::string &realm, const std::string scheme,
  326. boost::function<void (ClientRequest::ptr)> bodyDg)
  327. {
  328. authorize(request->request(), signatureMethod, clientCredentials,
  329. tokenCredentials, realm, scheme);
  330. if (bodyDg)
  331. bodyDg(request);
  332. else
  333. request->doRequest();
  334. }
  335. ClientRequest::ptr
  336. RequestBroker::request(Request &requestHeaders, bool forceNewConnection,
  337. boost::function<void (ClientRequest::ptr)> bodyDg)
  338. {
  339. ClientRequest::ptr priorRequest;
  340. std::pair<std::string, std::string> clientCredentials, tokenCredentials;
  341. std::string signatureMethod, realm;
  342. size_t attempts = 0;
  343. while (true) {
  344. boost::function<void (ClientRequest::ptr)> wrappedBodyDg = bodyDg;
  345. if (m_getCredentialsDg(requestHeaders.requestLine.uri, priorRequest,
  346. signatureMethod, clientCredentials, tokenCredentials, realm,
  347. attempts++))
  348. wrappedBodyDg = boost::bind(&authorizeDg, _1,
  349. boost::cref(signatureMethod), boost::cref(clientCredentials),
  350. boost::cref(tokenCredentials), boost::cref(realm),
  351. requestHeaders.requestLine.uri.scheme(),
  352. bodyDg);
  353. else if (priorRequest)
  354. return priorRequest;
  355. if (priorRequest)
  356. priorRequest->finish();
  357. priorRequest = parent()->request(requestHeaders, forceNewConnection,
  358. wrappedBodyDg);
  359. if (priorRequest->response().status.status == UNAUTHORIZED &&
  360. isAcceptable(priorRequest->response().response.wwwAuthenticate,
  361. "OAuth"))
  362. continue;
  363. return priorRequest;
  364. }
  365. }
  366. }}}