oauth2_mint_token_flow.cc 16 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452
  1. // Copyright (c) 2012 The Chromium Authors. All rights reserved.
  2. // Use of this source code is governed by a BSD-style license that can be
  3. // found in the LICENSE file.
  4. #include "google_apis/gaia/oauth2_mint_token_flow.h"
  5. #include <stddef.h>
  6. #include <set>
  7. #include <string>
  8. #include <vector>
  9. #include "base/bind.h"
  10. #include "base/command_line.h"
  11. #include "base/containers/span.h"
  12. #include "base/json/json_reader.h"
  13. #include "base/metrics/histogram_functions.h"
  14. #include "base/strings/escape.h"
  15. #include "base/strings/string_number_conversions.h"
  16. #include "base/strings/string_split.h"
  17. #include "base/strings/string_util.h"
  18. #include "base/strings/stringprintf.h"
  19. #include "base/strings/utf_string_conversions.h"
  20. #include "base/values.h"
  21. #include "google_apis/gaia/gaia_urls.h"
  22. #include "google_apis/gaia/google_service_auth_error.h"
  23. #include "net/base/net_errors.h"
  24. #include "net/cookies/cookie_constants.h"
  25. #include "services/network/public/mojom/url_response_head.mojom.h"
  26. #include "third_party/abseil-cpp/absl/types/optional.h"
  27. namespace {
  28. const char kValueFalse[] = "false";
  29. const char kValueTrue[] = "true";
  30. const char kResponseTypeValueNone[] = "none";
  31. const char kResponseTypeValueToken[] = "token";
  32. const char kOAuth2IssueTokenBodyFormat[] =
  33. "force=%s"
  34. "&response_type=%s"
  35. "&scope=%s"
  36. "&enable_granular_permissions=%s"
  37. "&client_id=%s"
  38. "&origin=%s"
  39. "&lib_ver=%s"
  40. "&release_channel=%s";
  41. const char kOAuth2IssueTokenBodyFormatSelectedUserIdAddendum[] =
  42. "&selected_user_id=%s";
  43. const char kOAuth2IssueTokenBodyFormatDeviceIdAddendum[] =
  44. "&device_id=%s&device_type=chrome";
  45. const char kOAuth2IssueTokenBodyFormatConsentResultAddendum[] =
  46. "&consent_result=%s";
  47. const char kIssueAdviceKey[] = "issueAdvice";
  48. const char kIssueAdviceValueRemoteConsent[] = "remoteConsent";
  49. const char kAccessTokenKey[] = "token";
  50. const char kExpiresInKey[] = "expiresIn";
  51. const char kGrantedScopesKey[] = "grantedScopes";
  52. const char kError[] = "error";
  53. const char kMessage[] = "message";
  54. static GoogleServiceAuthError CreateAuthError(
  55. int net_error,
  56. const network::mojom::URLResponseHead* head,
  57. std::unique_ptr<std::string> body) {
  58. if (net_error == net::ERR_ABORTED)
  59. return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
  60. if (net_error != net::OK) {
  61. DLOG(WARNING) << "Server returned error: errno " << net_error;
  62. return GoogleServiceAuthError::FromConnectionError(net_error);
  63. }
  64. std::string response_body;
  65. if (body)
  66. response_body = std::move(*body);
  67. absl::optional<base::Value> value = base::JSONReader::Read(response_body);
  68. if (!value || !value->is_dict()) {
  69. int http_response_code = -1;
  70. if (head && head->headers)
  71. http_response_code = head->headers->response_code();
  72. return GoogleServiceAuthError::FromUnexpectedServiceResponse(
  73. base::StringPrintf("Not able to parse a JSON object from "
  74. "a service response. "
  75. "HTTP Status of the response is: %d",
  76. http_response_code));
  77. }
  78. const base::Value* error = value->FindDictKey(kError);
  79. if (!error) {
  80. return GoogleServiceAuthError::FromUnexpectedServiceResponse(
  81. "Not able to find a detailed error in a service response.");
  82. }
  83. const std::string* message = error->FindStringKey(kMessage);
  84. if (!message) {
  85. return GoogleServiceAuthError::FromUnexpectedServiceResponse(
  86. "Not able to find an error message within a service error.");
  87. }
  88. return GoogleServiceAuthError::FromServiceError(*message);
  89. }
  90. bool AreCookiesEqual(const net::CanonicalCookie& lhs,
  91. const net::CanonicalCookie& rhs) {
  92. return lhs.IsEquivalent(rhs);
  93. }
  94. void RecordApiCallResult(OAuth2MintTokenApiCallResult result) {
  95. base::UmaHistogramEnumeration(kOAuth2MintTokenApiCallResultHistogram, result);
  96. }
  97. } // namespace
  98. const char kOAuth2MintTokenApiCallResultHistogram[] =
  99. "Signin.OAuth2MintToken.ApiCallResult";
  100. RemoteConsentResolutionData::RemoteConsentResolutionData() = default;
  101. RemoteConsentResolutionData::~RemoteConsentResolutionData() = default;
  102. RemoteConsentResolutionData::RemoteConsentResolutionData(
  103. const RemoteConsentResolutionData& other) = default;
  104. RemoteConsentResolutionData& RemoteConsentResolutionData::operator=(
  105. const RemoteConsentResolutionData& other) = default;
  106. bool RemoteConsentResolutionData::operator==(
  107. const RemoteConsentResolutionData& rhs) const {
  108. return url == rhs.url && std::equal(cookies.begin(), cookies.end(),
  109. rhs.cookies.begin(), &AreCookiesEqual);
  110. }
  111. OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
  112. OAuth2MintTokenFlow::Parameters::Parameters(
  113. const std::string& eid,
  114. const std::string& cid,
  115. const std::vector<std::string>& scopes_arg,
  116. bool enable_granular_permissions,
  117. const std::string& device_id,
  118. const std::string& selected_user_id,
  119. const std::string& consent_result,
  120. const std::string& version,
  121. const std::string& channel,
  122. Mode mode_arg)
  123. : extension_id(eid),
  124. client_id(cid),
  125. scopes(scopes_arg),
  126. enable_granular_permissions(enable_granular_permissions),
  127. device_id(device_id),
  128. selected_user_id(selected_user_id),
  129. consent_result(consent_result),
  130. version(version),
  131. channel(channel),
  132. mode(mode_arg) {}
  133. OAuth2MintTokenFlow::Parameters::Parameters(const Parameters& other) = default;
  134. OAuth2MintTokenFlow::Parameters::~Parameters() {}
  135. OAuth2MintTokenFlow::OAuth2MintTokenFlow(Delegate* delegate,
  136. const Parameters& parameters)
  137. : delegate_(delegate), parameters_(parameters) {}
  138. OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
  139. void OAuth2MintTokenFlow::ReportSuccess(
  140. const std::string& access_token,
  141. const std::set<std::string>& granted_scopes,
  142. int time_to_live) {
  143. if (delegate_)
  144. delegate_->OnMintTokenSuccess(access_token, granted_scopes, time_to_live);
  145. // |this| may already be deleted.
  146. }
  147. void OAuth2MintTokenFlow::ReportRemoteConsentSuccess(
  148. const RemoteConsentResolutionData& resolution_data) {
  149. if (delegate_)
  150. delegate_->OnRemoteConsentSuccess(resolution_data);
  151. // |this| may already be deleted;
  152. }
  153. void OAuth2MintTokenFlow::ReportFailure(
  154. const GoogleServiceAuthError& error) {
  155. if (delegate_)
  156. delegate_->OnMintTokenFailure(error);
  157. // |this| may already be deleted.
  158. }
  159. GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
  160. return GaiaUrls::GetInstance()->oauth2_issue_token_url();
  161. }
  162. std::string OAuth2MintTokenFlow::CreateApiCallBody() {
  163. const char* force_value = (parameters_.mode == MODE_MINT_TOKEN_FORCE ||
  164. parameters_.mode == MODE_RECORD_GRANT)
  165. ? kValueTrue
  166. : kValueFalse;
  167. const char* response_type_value =
  168. (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE ||
  169. parameters_.mode == MODE_MINT_TOKEN_FORCE)
  170. ? kResponseTypeValueToken : kResponseTypeValueNone;
  171. const char* enable_granular_permissions_value =
  172. parameters_.enable_granular_permissions ? kValueTrue : kValueFalse;
  173. std::string body = base::StringPrintf(
  174. kOAuth2IssueTokenBodyFormat,
  175. base::EscapeUrlEncodedData(force_value, true).c_str(),
  176. base::EscapeUrlEncodedData(response_type_value, true).c_str(),
  177. base::EscapeUrlEncodedData(base::JoinString(parameters_.scopes, " "),
  178. true)
  179. .c_str(),
  180. base::EscapeUrlEncodedData(enable_granular_permissions_value, true)
  181. .c_str(),
  182. base::EscapeUrlEncodedData(parameters_.client_id, true).c_str(),
  183. base::EscapeUrlEncodedData(parameters_.extension_id, true).c_str(),
  184. base::EscapeUrlEncodedData(parameters_.version, true).c_str(),
  185. base::EscapeUrlEncodedData(parameters_.channel, true).c_str());
  186. if (!parameters_.device_id.empty()) {
  187. body.append(base::StringPrintf(
  188. kOAuth2IssueTokenBodyFormatDeviceIdAddendum,
  189. base::EscapeUrlEncodedData(parameters_.device_id, true).c_str()));
  190. }
  191. if (!parameters_.selected_user_id.empty()) {
  192. body.append(base::StringPrintf(
  193. kOAuth2IssueTokenBodyFormatSelectedUserIdAddendum,
  194. base::EscapeUrlEncodedData(parameters_.selected_user_id, true)
  195. .c_str()));
  196. }
  197. if (!parameters_.consent_result.empty()) {
  198. body.append(base::StringPrintf(
  199. kOAuth2IssueTokenBodyFormatConsentResultAddendum,
  200. base::EscapeUrlEncodedData(parameters_.consent_result, true).c_str()));
  201. }
  202. return body;
  203. }
  204. void OAuth2MintTokenFlow::ProcessApiCallSuccess(
  205. const network::mojom::URLResponseHead* head,
  206. std::unique_ptr<std::string> body) {
  207. std::string response_body;
  208. if (body)
  209. response_body = std::move(*body);
  210. absl::optional<base::Value> value = base::JSONReader::Read(response_body);
  211. if (!value || !value->is_dict()) {
  212. RecordApiCallResult(OAuth2MintTokenApiCallResult::kParseJsonFailure);
  213. ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
  214. "Not able to parse a JSON object from a service response."));
  215. return;
  216. }
  217. std::string* issue_advice_value = value->FindStringKey(kIssueAdviceKey);
  218. if (!issue_advice_value) {
  219. RecordApiCallResult(
  220. OAuth2MintTokenApiCallResult::kIssueAdviceKeyNotFoundFailure);
  221. ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
  222. "Not able to find an issueAdvice in a service response."));
  223. return;
  224. }
  225. if (*issue_advice_value == kIssueAdviceValueRemoteConsent) {
  226. RemoteConsentResolutionData resolution_data;
  227. if (ParseRemoteConsentResponse(&(*value), &resolution_data)) {
  228. RecordApiCallResult(OAuth2MintTokenApiCallResult::kRemoteConsentSuccess);
  229. ReportRemoteConsentSuccess(resolution_data);
  230. } else {
  231. RecordApiCallResult(
  232. OAuth2MintTokenApiCallResult::kParseRemoteConsentFailure);
  233. ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
  234. "Not able to parse the contents of remote consent from a service "
  235. "response."));
  236. }
  237. return;
  238. }
  239. std::string access_token;
  240. std::set<std::string> granted_scopes;
  241. int time_to_live;
  242. if (ParseMintTokenResponse(&(*value), &access_token, &granted_scopes,
  243. &time_to_live)) {
  244. RecordApiCallResult(OAuth2MintTokenApiCallResult::kMintTokenSuccess);
  245. ReportSuccess(access_token, granted_scopes, time_to_live);
  246. } else {
  247. RecordApiCallResult(OAuth2MintTokenApiCallResult::kParseMintTokenFailure);
  248. ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
  249. "Not able to parse the contents of access token "
  250. "from a service response."));
  251. }
  252. // |this| may be deleted!
  253. }
  254. void OAuth2MintTokenFlow::ProcessApiCallFailure(
  255. int net_error,
  256. const network::mojom::URLResponseHead* head,
  257. std::unique_ptr<std::string> body) {
  258. RecordApiCallResult(OAuth2MintTokenApiCallResult::kApiCallFailure);
  259. ReportFailure(CreateAuthError(net_error, head, std::move(body)));
  260. }
  261. // static
  262. bool OAuth2MintTokenFlow::ParseMintTokenResponse(
  263. const base::Value* dict,
  264. std::string* access_token,
  265. std::set<std::string>* granted_scopes,
  266. int* time_to_live) {
  267. CHECK(dict);
  268. CHECK(dict->is_dict());
  269. CHECK(access_token);
  270. CHECK(granted_scopes);
  271. CHECK(time_to_live);
  272. const std::string* ttl_string = dict->FindStringKey(kExpiresInKey);
  273. if (!ttl_string || !base::StringToInt(*ttl_string, time_to_live))
  274. return false;
  275. const std::string* access_token_ptr = dict->FindStringKey(kAccessTokenKey);
  276. if (!access_token_ptr)
  277. return false;
  278. *access_token = *access_token_ptr;
  279. const std::string* granted_scopes_string =
  280. dict->FindStringKey(kGrantedScopesKey);
  281. if (!granted_scopes_string)
  282. return false;
  283. const std::vector<std::string> granted_scopes_vector =
  284. base::SplitString(*granted_scopes_string, " ", base::TRIM_WHITESPACE,
  285. base::SPLIT_WANT_NONEMPTY);
  286. if (granted_scopes_vector.empty())
  287. return false;
  288. const std::set<std::string> granted_scopes_set(granted_scopes_vector.begin(),
  289. granted_scopes_vector.end());
  290. *granted_scopes = std::move(granted_scopes_set);
  291. return true;
  292. }
  293. // static
  294. bool OAuth2MintTokenFlow::ParseRemoteConsentResponse(
  295. const base::Value* dict,
  296. RemoteConsentResolutionData* resolution_data) {
  297. CHECK(dict);
  298. CHECK(resolution_data);
  299. const base::Value* resolution_dict = dict->FindDictKey("resolutionData");
  300. if (!resolution_dict)
  301. return false;
  302. const std::string* resolution_approach =
  303. resolution_dict->FindStringKey("resolutionApproach");
  304. if (!resolution_approach || *resolution_approach != "resolveInBrowser")
  305. return false;
  306. const std::string* resolution_url_string =
  307. resolution_dict->FindStringKey("resolutionUrl");
  308. if (!resolution_url_string)
  309. return false;
  310. GURL resolution_url(*resolution_url_string);
  311. if (!resolution_url.is_valid())
  312. return false;
  313. const base::Value* browser_cookies =
  314. resolution_dict->FindListKey("browserCookies");
  315. base::span<const base::Value> cookie_list;
  316. if (browser_cookies)
  317. cookie_list = browser_cookies->GetListDeprecated();
  318. base::Time time_now = base::Time::Now();
  319. bool success = true;
  320. std::vector<net::CanonicalCookie> cookies;
  321. for (const auto& cookie_dict : cookie_list) {
  322. if (!cookie_dict.is_dict()) {
  323. success = false;
  324. break;
  325. }
  326. // Required parameters:
  327. const std::string* name = cookie_dict.FindStringKey("name");
  328. const std::string* value = cookie_dict.FindStringKey("value");
  329. const std::string* domain = cookie_dict.FindStringKey("domain");
  330. if (!name || !value || !domain) {
  331. success = false;
  332. break;
  333. }
  334. // Optional parameters:
  335. const std::string* path = cookie_dict.FindStringKey("path");
  336. const std::string* max_age_seconds =
  337. cookie_dict.FindStringKey("maxAgeSeconds");
  338. absl::optional<bool> is_secure = cookie_dict.FindBoolKey("isSecure");
  339. absl::optional<bool> is_http_only = cookie_dict.FindBoolKey("isHttpOnly");
  340. const std::string* same_site = cookie_dict.FindStringKey("sameSite");
  341. int64_t max_age = -1;
  342. if (max_age_seconds && !base::StringToInt64(*max_age_seconds, &max_age)) {
  343. success = false;
  344. break;
  345. }
  346. base::Time expiration_time = base::Time();
  347. if (max_age > 0)
  348. expiration_time = time_now + base::Seconds(max_age);
  349. std::unique_ptr<net::CanonicalCookie> cookie =
  350. net::CanonicalCookie::CreateSanitizedCookie(
  351. resolution_url, *name, *value, *domain, path ? *path : "/",
  352. time_now, expiration_time, time_now, is_secure ? *is_secure : false,
  353. is_http_only ? *is_http_only : false,
  354. net::StringToCookieSameSite(same_site ? *same_site : ""),
  355. net::COOKIE_PRIORITY_DEFAULT, /* same_party */ false,
  356. /* partition_key */ absl::nullopt);
  357. cookies.push_back(*cookie);
  358. }
  359. if (success) {
  360. resolution_data->url = std::move(resolution_url);
  361. resolution_data->cookies = std::move(cookies);
  362. }
  363. return success;
  364. }
  365. net::PartialNetworkTrafficAnnotationTag
  366. OAuth2MintTokenFlow::GetNetworkTrafficAnnotationTag() {
  367. return net::DefinePartialNetworkTrafficAnnotation(
  368. "oauth2_mint_token_flow", "oauth2_api_call_flow", R"(
  369. semantics {
  370. sender: "Chrome Identity API"
  371. description:
  372. "Requests a token from gaia allowing an app or extension to act as "
  373. "the user when calling other google APIs."
  374. trigger: "API call from the app/extension."
  375. data:
  376. "User's login token, the identity of a chrome app/extension, and a "
  377. "list of oauth scopes requested by the app/extension."
  378. destination: GOOGLE_OWNED_SERVICE
  379. }
  380. policy {
  381. setting:
  382. "This feature cannot be disabled by settings, however the request is "
  383. "made only for signed-in users."
  384. chrome_policy {
  385. SigninAllowed {
  386. policy_options {mode: MANDATORY}
  387. SigninAllowed: false
  388. }
  389. }
  390. })");
  391. }