| // 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 <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/json/json_reader.h" |
| #include "base/message_loop/message_loop.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/escape.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "net/url_request/url_request_status.h" |
| |
| using net::URLFetcher; |
| using net::URLRequestContextGetter; |
| using net::URLRequestStatus; |
| |
| namespace { |
| |
| const char kForceValueFalse[] = "false"; |
| const char kForceValueTrue[] = "true"; |
| const char kResponseTypeValueNone[] = "none"; |
| const char kResponseTypeValueToken[] = "token"; |
| |
| const char kOAuth2IssueTokenBodyFormat[] = |
| "force=%s" |
| "&response_type=%s" |
| "&scope=%s" |
| "&client_id=%s" |
| "&origin=%s"; |
| // TODO(pavely): lib_ver is passed to differentiate IssueToken requests from |
| // different code locations. Remove once device_id mismatch is understood. |
| // (crbug.com/481596) |
| const char kOAuth2IssueTokenBodyFormatDeviceIdAddendum[] = |
| "&device_id=%s&device_type=chrome&lib_ver=extension"; |
| const char kIssueAdviceKey[] = "issueAdvice"; |
| const char kIssueAdviceValueConsent[] = "consent"; |
| const char kAccessTokenKey[] = "token"; |
| const char kConsentKey[] = "consent"; |
| const char kExpiresInKey[] = "expiresIn"; |
| const char kScopesKey[] = "scopes"; |
| const char kDescriptionKey[] = "description"; |
| const char kDetailKey[] = "detail"; |
| const char kDetailSeparators[] = "\n"; |
| const char kError[] = "error"; |
| const char kMessage[] = "message"; |
| |
| static GoogleServiceAuthError CreateAuthError(const net::URLFetcher* source) { |
| URLRequestStatus status = source->GetStatus(); |
| if (status.status() == URLRequestStatus::CANCELED) { |
| return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); |
| } |
| if (status.status() == URLRequestStatus::FAILED) { |
| DLOG(WARNING) << "Server returned error: errno " << status.error(); |
| return GoogleServiceAuthError::FromConnectionError(status.error()); |
| } |
| |
| std::string response_body; |
| source->GetResponseAsString(&response_body); |
| scoped_ptr<base::Value> value = base::JSONReader::Read(response_body); |
| base::DictionaryValue* response; |
| if (!value.get() || !value->GetAsDictionary(&response)) { |
| return GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| base::StringPrintf( |
| "Not able to parse a JSON object from a service response. " |
| "HTTP Status of the response is: %d", source->GetResponseCode())); |
| } |
| base::DictionaryValue* error; |
| if (!response->GetDictionary(kError, &error)) { |
| return GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| "Not able to find a detailed error in a service response."); |
| } |
| std::string message; |
| if (!error->GetString(kMessage, &message)) { |
| return GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| "Not able to find an error message within a service error."); |
| } |
| return GoogleServiceAuthError::FromServiceError(message); |
| } |
| |
| } // namespace |
| |
| IssueAdviceInfoEntry::IssueAdviceInfoEntry() {} |
| IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {} |
| |
| bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const { |
| return description == rhs.description && details == rhs.details; |
| } |
| |
| 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, |
| const std::string& device_id, |
| Mode mode_arg) |
| : extension_id(eid), |
| client_id(cid), |
| scopes(scopes_arg), |
| device_id(device_id), |
| mode(mode_arg) { |
| } |
| |
| OAuth2MintTokenFlow::Parameters::~Parameters() {} |
| |
| OAuth2MintTokenFlow::OAuth2MintTokenFlow(Delegate* delegate, |
| const Parameters& parameters) |
| : delegate_(delegate), parameters_(parameters), weak_factory_(this) { |
| } |
| |
| OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { } |
| |
| void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token, |
| int time_to_live) { |
| if (delegate_) |
| delegate_->OnMintTokenSuccess(access_token, time_to_live); |
| |
| // |this| may already be deleted. |
| } |
| |
| void OAuth2MintTokenFlow::ReportIssueAdviceSuccess( |
| const IssueAdviceInfo& issue_advice) { |
| if (delegate_) |
| delegate_->OnIssueAdviceSuccess(issue_advice); |
| |
| // |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) |
| ? kForceValueTrue : kForceValueFalse; |
| const char* response_type_value = |
| (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE || |
| parameters_.mode == MODE_MINT_TOKEN_FORCE) |
| ? kResponseTypeValueToken : kResponseTypeValueNone; |
| std::string body = base::StringPrintf( |
| kOAuth2IssueTokenBodyFormat, |
| net::EscapeUrlEncodedData(force_value, true).c_str(), |
| net::EscapeUrlEncodedData(response_type_value, true).c_str(), |
| net::EscapeUrlEncodedData( |
| base::JoinString(parameters_.scopes, " "), true).c_str(), |
| net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(), |
| net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str()); |
| if (!parameters_.device_id.empty()) { |
| body.append(base::StringPrintf( |
| kOAuth2IssueTokenBodyFormatDeviceIdAddendum, |
| net::EscapeUrlEncodedData(parameters_.device_id, true).c_str())); |
| } |
| return body; |
| } |
| |
| void OAuth2MintTokenFlow::ProcessApiCallSuccess( |
| const net::URLFetcher* source) { |
| std::string response_body; |
| source->GetResponseAsString(&response_body); |
| scoped_ptr<base::Value> value = base::JSONReader::Read(response_body); |
| base::DictionaryValue* dict = NULL; |
| if (!value.get() || !value->GetAsDictionary(&dict)) { |
| ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| "Not able to parse a JSON object from a service response.")); |
| return; |
| } |
| |
| std::string issue_advice_value; |
| if (!dict->GetString(kIssueAdviceKey, &issue_advice_value)) { |
| ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| "Not able to find an issueAdvice in a service response.")); |
| return; |
| } |
| if (issue_advice_value == kIssueAdviceValueConsent) { |
| IssueAdviceInfo issue_advice; |
| if (ParseIssueAdviceResponse(dict, &issue_advice)) |
| ReportIssueAdviceSuccess(issue_advice); |
| else |
| ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| "Not able to parse the contents of consent " |
| "from a service response.")); |
| } else { |
| std::string access_token; |
| int time_to_live; |
| if (ParseMintTokenResponse(dict, &access_token, &time_to_live)) |
| ReportSuccess(access_token, time_to_live); |
| else |
| ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( |
| "Not able to parse the contents of access token " |
| "from a service response.")); |
| } |
| |
| // |this| may be deleted! |
| } |
| |
| void OAuth2MintTokenFlow::ProcessApiCallFailure( |
| const net::URLFetcher* source) { |
| ReportFailure(CreateAuthError(source)); |
| } |
| |
| // static |
| bool OAuth2MintTokenFlow::ParseMintTokenResponse( |
| const base::DictionaryValue* dict, std::string* access_token, |
| int* time_to_live) { |
| CHECK(dict); |
| CHECK(access_token); |
| CHECK(time_to_live); |
| std::string ttl_string; |
| return dict->GetString(kExpiresInKey, &ttl_string) && |
| base::StringToInt(ttl_string, time_to_live) && |
| dict->GetString(kAccessTokenKey, access_token); |
| } |
| |
| // static |
| bool OAuth2MintTokenFlow::ParseIssueAdviceResponse( |
| const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) { |
| CHECK(dict); |
| CHECK(issue_advice); |
| |
| const base::DictionaryValue* consent_dict = NULL; |
| if (!dict->GetDictionary(kConsentKey, &consent_dict)) |
| return false; |
| |
| const base::ListValue* scopes_list = NULL; |
| if (!consent_dict->GetList(kScopesKey, &scopes_list)) |
| return false; |
| |
| bool success = true; |
| for (size_t index = 0; index < scopes_list->GetSize(); ++index) { |
| const base::DictionaryValue* scopes_entry = NULL; |
| IssueAdviceInfoEntry entry; |
| base::string16 detail; |
| if (!scopes_list->GetDictionary(index, &scopes_entry) || |
| !scopes_entry->GetString(kDescriptionKey, &entry.description) || |
| !scopes_entry->GetString(kDetailKey, &detail)) { |
| success = false; |
| break; |
| } |
| |
| base::TrimWhitespace(entry.description, base::TRIM_ALL, &entry.description); |
| entry.details = base::SplitString( |
| detail, base::ASCIIToUTF16(kDetailSeparators), |
| base::TRIM_WHITESPACE, base::SPLIT_WANT_NONEMPTY); |
| issue_advice->push_back(entry); |
| } |
| |
| if (!success) |
| issue_advice->clear(); |
| |
| return success; |
| } |