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