// 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 #include #include #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 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& 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 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; }