oauth2_mint_token_flow.cc revision 7dbb3d5cf0c15f500944d211057644d6a2f37371
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 5#include "google_apis/gaia/oauth2_mint_token_flow.h" 6 7#include <string> 8#include <vector> 9 10#include "base/basictypes.h" 11#include "base/bind.h" 12#include "base/command_line.h" 13#include "base/json/json_reader.h" 14#include "base/message_loop.h" 15#include "base/strings/string_number_conversions.h" 16#include "base/strings/string_util.h" 17#include "base/strings/stringprintf.h" 18#include "base/strings/utf_string_conversions.h" 19#include "base/values.h" 20#include "google_apis/gaia/gaia_urls.h" 21#include "google_apis/gaia/google_service_auth_error.h" 22#include "net/base/escape.h" 23#include "net/url_request/url_fetcher.h" 24#include "net/url_request/url_request_context_getter.h" 25#include "net/url_request/url_request_status.h" 26 27using net::URLFetcher; 28using net::URLRequestContextGetter; 29using net::URLRequestStatus; 30 31namespace { 32 33static const char kForceValueFalse[] = "false"; 34static const char kForceValueTrue[] = "true"; 35static const char kResponseTypeValueNone[] = "none"; 36static const char kResponseTypeValueToken[] = "token"; 37 38static const char kOAuth2IssueTokenBodyFormat[] = 39 "force=%s" 40 "&response_type=%s" 41 "&scope=%s" 42 "&client_id=%s" 43 "&origin=%s"; 44static const char kIssueAdviceKey[] = "issueAdvice"; 45static const char kIssueAdviceValueAuto[] = "auto"; 46static const char kIssueAdviceValueConsent[] = "consent"; 47static const char kAccessTokenKey[] = "token"; 48static const char kConsentKey[] = "consent"; 49static const char kExpiresInKey[] = "expiresIn"; 50static const char kScopesKey[] = "scopes"; 51static const char kDescriptionKey[] = "description"; 52static const char kDetailKey[] = "detail"; 53static const char kDetailSeparators[] = "\n"; 54static const char kError[] = "error"; 55static const char kMessage[] = "message"; 56 57static GoogleServiceAuthError CreateAuthError(const net::URLFetcher* source) { 58 URLRequestStatus status = source->GetStatus(); 59 if (status.status() == URLRequestStatus::CANCELED) { 60 return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED); 61 } 62 if (status.status() == URLRequestStatus::FAILED) { 63 DLOG(WARNING) << "Server returned error: errno " << status.error(); 64 return GoogleServiceAuthError::FromConnectionError(status.error()); 65 } 66 67 std::string response_body; 68 source->GetResponseAsString(&response_body); 69 scoped_ptr<Value> value(base::JSONReader::Read(response_body)); 70 DictionaryValue* response; 71 if (!value.get() || !value->GetAsDictionary(&response)) { 72 return GoogleServiceAuthError::FromUnexpectedServiceResponse( 73 base::StringPrintf( 74 "Not able to parse a JSON object from a service response. " 75 "HTTP Status of the response is: %d", source->GetResponseCode())); 76 } 77 DictionaryValue* error; 78 if (!response->GetDictionary(kError, &error)) { 79 return GoogleServiceAuthError::FromUnexpectedServiceResponse( 80 "Not able to find a detailed error in a service response."); 81 } 82 std::string message; 83 if (!error->GetString(kMessage, &message)) { 84 return GoogleServiceAuthError::FromUnexpectedServiceResponse( 85 "Not able to find an error message within a service error."); 86 } 87 return GoogleServiceAuthError::FromServiceError(message); 88} 89 90} // namespace 91 92IssueAdviceInfoEntry::IssueAdviceInfoEntry() {} 93IssueAdviceInfoEntry::~IssueAdviceInfoEntry() {} 94 95bool IssueAdviceInfoEntry::operator ==(const IssueAdviceInfoEntry& rhs) const { 96 return description == rhs.description && details == rhs.details; 97} 98 99OAuth2MintTokenFlow::Parameters::Parameters() : mode(MODE_ISSUE_ADVICE) {} 100 101OAuth2MintTokenFlow::Parameters::Parameters( 102 const std::string& at, 103 const std::string& eid, 104 const std::string& cid, 105 const std::vector<std::string>& scopes_arg, 106 Mode mode_arg) 107 : access_token(at), 108 extension_id(eid), 109 client_id(cid), 110 scopes(scopes_arg), 111 mode(mode_arg) { 112} 113 114OAuth2MintTokenFlow::Parameters::~Parameters() {} 115 116OAuth2MintTokenFlow::OAuth2MintTokenFlow(URLRequestContextGetter* context, 117 Delegate* delegate, 118 const Parameters& parameters) 119 : OAuth2ApiCallFlow(context, 120 std::string(), 121 parameters.access_token, 122 std::vector<std::string>()), 123 delegate_(delegate), 124 parameters_(parameters), 125 weak_factory_(this) {} 126 127OAuth2MintTokenFlow::~OAuth2MintTokenFlow() { } 128 129void OAuth2MintTokenFlow::ReportSuccess(const std::string& access_token, 130 int time_to_live) { 131 if (delegate_) 132 delegate_->OnMintTokenSuccess(access_token, time_to_live); 133 134 // |this| may already be deleted. 135} 136 137void OAuth2MintTokenFlow::ReportIssueAdviceSuccess( 138 const IssueAdviceInfo& issue_advice) { 139 if (delegate_) 140 delegate_->OnIssueAdviceSuccess(issue_advice); 141 142 // |this| may already be deleted. 143} 144 145void OAuth2MintTokenFlow::ReportFailure( 146 const GoogleServiceAuthError& error) { 147 if (delegate_) 148 delegate_->OnMintTokenFailure(error); 149 150 // |this| may already be deleted. 151} 152 153GURL OAuth2MintTokenFlow::CreateApiCallUrl() { 154 return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url()); 155} 156 157std::string OAuth2MintTokenFlow::CreateApiCallBody() { 158 const char* force_value = 159 (parameters_.mode == MODE_MINT_TOKEN_FORCE || 160 parameters_.mode == MODE_RECORD_GRANT) 161 ? kForceValueTrue : kForceValueFalse; 162 const char* response_type_value = 163 (parameters_.mode == MODE_MINT_TOKEN_NO_FORCE || 164 parameters_.mode == MODE_MINT_TOKEN_FORCE) 165 ? kResponseTypeValueToken : kResponseTypeValueNone; 166 return base::StringPrintf( 167 kOAuth2IssueTokenBodyFormat, 168 net::EscapeUrlEncodedData(force_value, true).c_str(), 169 net::EscapeUrlEncodedData(response_type_value, true).c_str(), 170 net::EscapeUrlEncodedData( 171 JoinString(parameters_.scopes, ' '), true).c_str(), 172 net::EscapeUrlEncodedData(parameters_.client_id, true).c_str(), 173 net::EscapeUrlEncodedData(parameters_.extension_id, true).c_str()); 174} 175 176void OAuth2MintTokenFlow::ProcessApiCallSuccess( 177 const net::URLFetcher* source) { 178 std::string response_body; 179 source->GetResponseAsString(&response_body); 180 scoped_ptr<base::Value> value(base::JSONReader::Read(response_body)); 181 base::DictionaryValue* dict = NULL; 182 if (!value.get() || !value->GetAsDictionary(&dict)) { 183 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( 184 "Not able to parse a JSON object from a service response.")); 185 return; 186 } 187 188 std::string issue_advice_value; 189 if (!dict->GetString(kIssueAdviceKey, &issue_advice_value)) { 190 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( 191 "Not able to find an issueAdvice in a service response.")); 192 return; 193 } 194 if (issue_advice_value == kIssueAdviceValueConsent) { 195 IssueAdviceInfo issue_advice; 196 if (ParseIssueAdviceResponse(dict, &issue_advice)) 197 ReportIssueAdviceSuccess(issue_advice); 198 else 199 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( 200 "Not able to parse the contents of consent " 201 "from a service response.")); 202 } else { 203 std::string access_token; 204 int time_to_live; 205 if (ParseMintTokenResponse(dict, &access_token, &time_to_live)) 206 ReportSuccess(access_token, time_to_live); 207 else 208 ReportFailure(GoogleServiceAuthError::FromUnexpectedServiceResponse( 209 "Not able to parse the contents of access token " 210 "from a service response.")); 211 } 212 213 // |this| may be deleted! 214} 215 216void OAuth2MintTokenFlow::ProcessApiCallFailure( 217 const net::URLFetcher* source) { 218 ReportFailure(CreateAuthError(source)); 219} 220void OAuth2MintTokenFlow::ProcessNewAccessToken( 221 const std::string& access_token) { 222 // We don't currently store new access tokens. We generate one every time. 223 // So we have nothing to do here. 224 return; 225} 226void OAuth2MintTokenFlow::ProcessMintAccessTokenFailure( 227 const GoogleServiceAuthError& error) { 228 ReportFailure(error); 229} 230 231// static 232bool OAuth2MintTokenFlow::ParseMintTokenResponse( 233 const base::DictionaryValue* dict, std::string* access_token, 234 int* time_to_live) { 235 CHECK(dict); 236 CHECK(access_token); 237 CHECK(time_to_live); 238 std::string ttl_string; 239 return dict->GetString(kExpiresInKey, &ttl_string) && 240 base::StringToInt(ttl_string, time_to_live) && 241 dict->GetString(kAccessTokenKey, access_token); 242} 243 244// static 245bool OAuth2MintTokenFlow::ParseIssueAdviceResponse( 246 const base::DictionaryValue* dict, IssueAdviceInfo* issue_advice) { 247 CHECK(dict); 248 CHECK(issue_advice); 249 250 const base::DictionaryValue* consent_dict = NULL; 251 if (!dict->GetDictionary(kConsentKey, &consent_dict)) 252 return false; 253 254 const base::ListValue* scopes_list = NULL; 255 if (!consent_dict->GetList(kScopesKey, &scopes_list)) 256 return false; 257 258 bool success = true; 259 for (size_t index = 0; index < scopes_list->GetSize(); ++index) { 260 const base::DictionaryValue* scopes_entry = NULL; 261 IssueAdviceInfoEntry entry; 262 string16 detail; 263 if (!scopes_list->GetDictionary(index, &scopes_entry) || 264 !scopes_entry->GetString(kDescriptionKey, &entry.description) || 265 !scopes_entry->GetString(kDetailKey, &detail)) { 266 success = false; 267 break; 268 } 269 270 TrimWhitespace(entry.description, TRIM_ALL, &entry.description); 271 static const string16 detail_separators = ASCIIToUTF16(kDetailSeparators); 272 Tokenize(detail, detail_separators, &entry.details); 273 for (size_t i = 0; i < entry.details.size(); i++) 274 TrimWhitespace(entry.details[i], TRIM_ALL, &entry.details[i]); 275 issue_advice->push_back(entry); 276 } 277 278 if (!success) 279 issue_advice->clear(); 280 281 return success; 282} 283