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