123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452 |
- // Copyright (c) 2012 The Chromium Authors. All rights reserved.
- // Use of this source code is governed by a BSD-style license that can be
- // found in the LICENSE file.
- #include "google_apis/gaia/oauth2_mint_token_flow.h"
- #include <stddef.h>
- #include <set>
- #include <string>
- #include <vector>
- #include "base/bind.h"
- #include "base/command_line.h"
- #include "base/containers/span.h"
- #include "base/json/json_reader.h"
- #include "base/metrics/histogram_functions.h"
- #include "base/strings/escape.h"
- #include "base/strings/string_number_conversions.h"
- #include "base/strings/string_split.h"
- #include "base/strings/string_util.h"
- #include "base/strings/stringprintf.h"
- #include "base/strings/utf_string_conversions.h"
- #include "base/values.h"
- #include "google_apis/gaia/gaia_urls.h"
- #include "google_apis/gaia/google_service_auth_error.h"
- #include "net/base/net_errors.h"
- #include "net/cookies/cookie_constants.h"
- #include "services/network/public/mojom/url_response_head.mojom.h"
- #include "third_party/abseil-cpp/absl/types/optional.h"
- namespace {
- const char kValueFalse[] = "false";
- const char kValueTrue[] = "true";
- const char kResponseTypeValueNone[] = "none";
- const char kResponseTypeValueToken[] = "token";
- const char kOAuth2IssueTokenBodyFormat[] =
- "force=%s"
- "&response_type=%s"
- "&scope=%s"
- "&enable_granular_permissions=%s"
- "&client_id=%s"
- "&origin=%s"
- "&lib_ver=%s"
- "&release_channel=%s";
- const char kOAuth2IssueTokenBodyFormatSelectedUserIdAddendum[] =
- "&selected_user_id=%s";
- const char kOAuth2IssueTokenBodyFormatDeviceIdAddendum[] =
- "&device_id=%s&device_type=chrome";
- const char kOAuth2IssueTokenBodyFormatConsentResultAddendum[] =
- "&consent_result=%s";
- const char kIssueAdviceKey[] = "issueAdvice";
- const char kIssueAdviceValueRemoteConsent[] = "remoteConsent";
- const char kAccessTokenKey[] = "token";
- const char kExpiresInKey[] = "expiresIn";
- const char kGrantedScopesKey[] = "grantedScopes";
- const char kError[] = "error";
- const char kMessage[] = "message";
- static GoogleServiceAuthError CreateAuthError(
- int net_error,
- const network::mojom::URLResponseHead* head,
- std::unique_ptr<std::string> body) {
- if (net_error == net::ERR_ABORTED)
- return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
- if (net_error != net::OK) {
- DLOG(WARNING) << "Server returned error: errno " << net_error;
- return GoogleServiceAuthError::FromConnectionError(net_error);
- }
- std::string response_body;
- if (body)
- response_body = std::move(*body);
- absl::optional<base::Value> value = base::JSONReader::Read(response_body);
- if (!value || !value->is_dict()) {
- int http_response_code = -1;
- if (head && head->headers)
- http_response_code = head->headers->response_code();
- return GoogleServiceAuthError::FromUnexpectedServiceResponse(
- base::StringPrintf("Not able to parse a JSON object from "
- "a service response. "
- "HTTP Status of the response is: %d",
- http_response_code));
- }
- const base::Value* error = value->FindDictKey(kError);
- if (!error) {
- return GoogleServiceAuthError::FromUnexpectedServiceResponse(
- "Not able to find a detailed error in a service response.");
- }
- const std::string* message = error->FindStringKey(kMessage);
- if (!message) {
- return GoogleServiceAuthError::FromUnexpectedServiceResponse(
- "Not able to find an error message within a service error.");
- }
- return GoogleServiceAuthError::FromServiceError(*message);
- }
- bool AreCookiesEqual(const net::CanonicalCookie& lhs,
- const net::CanonicalCookie& rhs) {
- return lhs.IsEquivalent(rhs);
- }
- void RecordApiCallResult(OAuth2MintTokenApiCallResult result) {
- base::UmaHistogramEnumeration(kOAuth2MintTokenApiCallResultHistogram, result);
- }
- } // namespace
- const char kOAuth2MintTokenApiCallResultHistogram[] =
- "Signin.OAuth2MintToken.ApiCallResult";
- RemoteConsentResolutionData::RemoteConsentResolutionData() = default;
- RemoteConsentResolutionData::~RemoteConsentResolutionData() = default;
- RemoteConsentResolutionData::RemoteConsentResolutionData(
- const RemoteConsentResolutionData& other) = default;
- RemoteConsentResolutionData& RemoteConsentResolutionData::operator=(
- const RemoteConsentResolutionData& other) = default;
- bool RemoteConsentResolutionData::operator==(
- const RemoteConsentResolutionData& rhs) const {
- return url == rhs.url && std::equal(cookies.begin(), cookies.end(),
- rhs.cookies.begin(), &AreCookiesEqual);
- }
- OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {}
- OAuth2MintTokenFlow::Parameters::Parameters(
- const std::string& eid,
- const std::string& cid,
- const std::vector<std::string>& scopes_arg,
- bool enable_granular_permissions,
- const std::string& device_id,
- const std::string& selected_user_id,
- const std::string& consent_result,
- const std::string& version,
- const std::string& channel,
- Mode mode_arg)
- : extension_id(eid),
- client_id(cid),
- scopes(scopes_arg),
- enable_granular_permissions(enable_granular_permissions),
- device_id(device_id),
- selected_user_id(selected_user_id),
- consent_result(consent_result),
- version(version),
- channel(channel),
- mode(mode_arg) {}
- OAuth2MintTokenFlow::Parameters::Parameters(const Parameters& other) = default;
- OAuth2MintTokenFlow::Parameters::~Parameters() {}
- OAuth2MintTokenFlow::OAuth2MintTokenFlow(Delegate* delegate,
- const Parameters& parameters)
- : delegate_(delegate), parameters_(parameters) {}
- OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { }
- void OAuth2MintTokenFlow::ReportSuccess(
- const std::string& access_token,
- const std::set<std::string>& granted_scopes,
- int time_to_live) {
- if (delegate_)
- delegate_->OnMintTokenSuccess(access_token, granted_scopes, time_to_live);
- // |this| may already be deleted.
- }
- void OAuth2MintTokenFlow::ReportRemoteConsentSuccess(
- const RemoteConsentResolutionData& resolution_data) {
- if (delegate_)
- delegate_->OnRemoteConsentSuccess(resolution_data);
- // |this| may already be deleted;
- }
- void OAuth2MintTokenFlow::ReportFailure(
- const GoogleServiceAuthError& error) {
- if (delegate_)
- delegate_->OnMintTokenFailure(error);
- // |this| may already be deleted.
- }
- GURL OAuth2MintTokenFlow::CreateApiCallUrl() {
- return GaiaUrls::GetInstance()->oauth2_issue_token_url();
- }
- std::string OAuth2MintTokenFlow::CreateApiCallBody() {
- const char* force_value = (parameters_.mode == MODE_MINT_TOKEN_FORCE ||
- parameters_.mode == MODE_RECORD_GRANT)
- ? kValueTrue
- : kValueFalse;
- const char* response_type_value =
- (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE ||
- parameters_.mode == MODE_MINT_TOKEN_FORCE)
- ? kResponseTypeValueToken : kResponseTypeValueNone;
- const char* enable_granular_permissions_value =
- parameters_.enable_granular_permissions ? kValueTrue : kValueFalse;
- std::string body = base::StringPrintf(
- kOAuth2IssueTokenBodyFormat,
- base::EscapeUrlEncodedData(force_value, true).c_str(),
- base::EscapeUrlEncodedData(response_type_value, true).c_str(),
- base::EscapeUrlEncodedData(base::JoinString(parameters_.scopes, " "),
- true)
- .c_str(),
- base::EscapeUrlEncodedData(enable_granular_permissions_value, true)
- .c_str(),
- base::EscapeUrlEncodedData(parameters_.client_id, true).c_str(),
- base::EscapeUrlEncodedData(parameters_.extension_id, true).c_str(),
- base::EscapeUrlEncodedData(parameters_.version, true).c_str(),
- base::EscapeUrlEncodedData(parameters_.channel, true).c_str());
- if (!parameters_.device_id.empty()) {
- body.append(base::StringPrintf(
- kOAuth2IssueTokenBodyFormatDeviceIdAddendum,
- base::EscapeUrlEncodedData(parameters_.device_id, true).c_str()));
- }
- if (!parameters_.selected_user_id.empty()) {
- body.append(base::StringPrintf(
- kOAuth2IssueTokenBodyFormatSelectedUserIdAddendum,
- base::EscapeUrlEncodedData(parameters_.selected_user_id, true)
- .c_str()));
- }
- if (!parameters_.consent_result.empty()) {
- body.append(base::StringPrintf(
- kOAuth2IssueTokenBodyFormatConsentResultAddendum,
- base::EscapeUrlEncodedData(parameters_.consent_result, true).c_str()));
- }
- return body;
- }
- void OAuth2MintTokenFlow::ProcessApiCallSuccess(
- const network::mojom::URLResponseHead* head,
- std::unique_ptr<std::string> body) {
- std::string response_body;
- if (body)
- response_body = std::move(*body);
- absl::optional<base::Value> value = base::JSONReader::Read(response_body);
- if (!value || !value->is_dict()) {
- RecordApiCallResult(OAuth2MintTokenApiCallResult::kParseJsonFailure);
- ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
- "Not able to parse a JSON object from a service response."));
- return;
- }
- std::string* issue_advice_value = value->FindStringKey(kIssueAdviceKey);
- if (!issue_advice_value) {
- RecordApiCallResult(
- OAuth2MintTokenApiCallResult::kIssueAdviceKeyNotFoundFailure);
- ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
- "Not able to find an issueAdvice in a service response."));
- return;
- }
- if (*issue_advice_value == kIssueAdviceValueRemoteConsent) {
- RemoteConsentResolutionData resolution_data;
- if (ParseRemoteConsentResponse(&(*value), &resolution_data)) {
- RecordApiCallResult(OAuth2MintTokenApiCallResult::kRemoteConsentSuccess);
- ReportRemoteConsentSuccess(resolution_data);
- } else {
- RecordApiCallResult(
- OAuth2MintTokenApiCallResult::kParseRemoteConsentFailure);
- ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
- "Not able to parse the contents of remote consent from a service "
- "response."));
- }
- return;
- }
- std::string access_token;
- std::set<std::string> granted_scopes;
- int time_to_live;
- if (ParseMintTokenResponse(&(*value), &access_token, &granted_scopes,
- &time_to_live)) {
- RecordApiCallResult(OAuth2MintTokenApiCallResult::kMintTokenSuccess);
- ReportSuccess(access_token, granted_scopes, time_to_live);
- } else {
- RecordApiCallResult(OAuth2MintTokenApiCallResult::kParseMintTokenFailure);
- ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse(
- "Not able to parse the contents of access token "
- "from a service response."));
- }
- // |this| may be deleted!
- }
- void OAuth2MintTokenFlow::ProcessApiCallFailure(
- int net_error,
- const network::mojom::URLResponseHead* head,
- std::unique_ptr<std::string> body) {
- RecordApiCallResult(OAuth2MintTokenApiCallResult::kApiCallFailure);
- ReportFailure(CreateAuthError(net_error, head, std::move(body)));
- }
- // static
- bool OAuth2MintTokenFlow::ParseMintTokenResponse(
- const base::Value* dict,
- std::string* access_token,
- std::set<std::string>* granted_scopes,
- int* time_to_live) {
- CHECK(dict);
- CHECK(dict->is_dict());
- CHECK(access_token);
- CHECK(granted_scopes);
- CHECK(time_to_live);
- const std::string* ttl_string = dict->FindStringKey(kExpiresInKey);
- if (!ttl_string || !base::StringToInt(*ttl_string, time_to_live))
- return false;
- const std::string* access_token_ptr = dict->FindStringKey(kAccessTokenKey);
- if (!access_token_ptr)
- return false;
- *access_token = *access_token_ptr;
- const std::string* granted_scopes_string =
- dict->FindStringKey(kGrantedScopesKey);
- if (!granted_scopes_string)
- return false;
- const std::vector<std::string> granted_scopes_vector =
- base::SplitString(*granted_scopes_string, " ", base::TRIM_WHITESPACE,
- base::SPLIT_WANT_NONEMPTY);
- if (granted_scopes_vector.empty())
- return false;
- const std::set<std::string> granted_scopes_set(granted_scopes_vector.begin(),
- granted_scopes_vector.end());
- *granted_scopes = std::move(granted_scopes_set);
- return true;
- }
- // static
- bool OAuth2MintTokenFlow::ParseRemoteConsentResponse(
- const base::Value* dict,
- RemoteConsentResolutionData* resolution_data) {
- CHECK(dict);
- CHECK(resolution_data);
- const base::Value* resolution_dict = dict->FindDictKey("resolutionData");
- if (!resolution_dict)
- return false;
- const std::string* resolution_approach =
- resolution_dict->FindStringKey("resolutionApproach");
- if (!resolution_approach || *resolution_approach != "resolveInBrowser")
- return false;
- const std::string* resolution_url_string =
- resolution_dict->FindStringKey("resolutionUrl");
- if (!resolution_url_string)
- return false;
- GURL resolution_url(*resolution_url_string);
- if (!resolution_url.is_valid())
- return false;
- const base::Value* browser_cookies =
- resolution_dict->FindListKey("browserCookies");
- base::span<const base::Value> cookie_list;
- if (browser_cookies)
- cookie_list = browser_cookies->GetListDeprecated();
- base::Time time_now = base::Time::Now();
- bool success = true;
- std::vector<net::CanonicalCookie> cookies;
- for (const auto& cookie_dict : cookie_list) {
- if (!cookie_dict.is_dict()) {
- success = false;
- break;
- }
- // Required parameters:
- const std::string* name = cookie_dict.FindStringKey("name");
- const std::string* value = cookie_dict.FindStringKey("value");
- const std::string* domain = cookie_dict.FindStringKey("domain");
- if (!name || !value || !domain) {
- success = false;
- break;
- }
- // Optional parameters:
- const std::string* path = cookie_dict.FindStringKey("path");
- const std::string* max_age_seconds =
- cookie_dict.FindStringKey("maxAgeSeconds");
- absl::optional<bool> is_secure = cookie_dict.FindBoolKey("isSecure");
- absl::optional<bool> is_http_only = cookie_dict.FindBoolKey("isHttpOnly");
- const std::string* same_site = cookie_dict.FindStringKey("sameSite");
- int64_t max_age = -1;
- if (max_age_seconds && !base::StringToInt64(*max_age_seconds, &max_age)) {
- success = false;
- break;
- }
- base::Time expiration_time = base::Time();
- if (max_age > 0)
- expiration_time = time_now + base::Seconds(max_age);
- std::unique_ptr<net::CanonicalCookie> cookie =
- net::CanonicalCookie::CreateSanitizedCookie(
- resolution_url, *name, *value, *domain, path ? *path : "/",
- time_now, expiration_time, time_now, is_secure ? *is_secure : false,
- is_http_only ? *is_http_only : false,
- net::StringToCookieSameSite(same_site ? *same_site : ""),
- net::COOKIE_PRIORITY_DEFAULT, /* same_party */ false,
- /* partition_key */ absl::nullopt);
- cookies.push_back(*cookie);
- }
- if (success) {
- resolution_data->url = std::move(resolution_url);
- resolution_data->cookies = std::move(cookies);
- }
- return success;
- }
- net::PartialNetworkTrafficAnnotationTag
- OAuth2MintTokenFlow::GetNetworkTrafficAnnotationTag() {
- return net::DefinePartialNetworkTrafficAnnotation(
- "oauth2_mint_token_flow", "oauth2_api_call_flow", R"(
- semantics {
- sender: "Chrome Identity API"
- description:
- "Requests a token from gaia allowing an app or extension to act as "
- "the user when calling other google APIs."
- trigger: "API call from the app/extension."
- data:
- "User's login token, the identity of a chrome app/extension, and a "
- "list of oauth scopes requested by the app/extension."
- destination: GOOGLE_OWNED_SERVICE
- }
- policy {
- setting:
- "This feature cannot be disabled by settings, however the request is "
- "made only for signed-in users."
- chrome_policy {
- SigninAllowed {
- policy_options {mode: MANDATORY}
- SigninAllowed: false
- }
- }
- })");
- }
|