1 // Copyright 2013 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 "components/autofill/content/browser/wallet/wallet_signin_helper.h" 6 7 #include "base/callback_helpers.h" 8 #include "base/json/json_reader.h" 9 #include "base/logging.h" 10 #include "base/rand_util.h" 11 #include "base/strings/string_util.h" 12 #include "base/strings/stringprintf.h" 13 #include "base/time/time.h" 14 #include "base/values.h" 15 #include "components/autofill/content/browser/wallet/wallet_service_url.h" 16 #include "components/autofill/content/browser/wallet/wallet_signin_helper_delegate.h" 17 #include "content/public/browser/browser_thread.h" 18 #include "google_apis/gaia/google_service_auth_error.h" 19 #include "net/base/escape.h" 20 #include "net/cookies/canonical_cookie.h" 21 #include "net/cookies/cookie_monster.h" 22 #include "net/cookies/cookie_options.h" 23 #include "net/cookies/cookie_store.h" 24 #include "net/url_request/url_fetcher.h" 25 #include "net/url_request/url_request_context.h" 26 #include "net/url_request/url_request_context_getter.h" 27 28 namespace autofill { 29 namespace wallet { 30 31 namespace { 32 33 // Toolbar::GetAccountInfo API URL (JSON). 34 const char kGetAccountInfoUrlFormat[] = 35 "https://clients1.google.com/tbproxy/getaccountinfo?key=%d&rv=2&requestor=chrome"; 36 37 const char kWalletCookieName[] = "gdtoken"; 38 39 // Callback for retrieving Google Wallet cookies. |callback| is passed the 40 // retrieved cookies and posted back to the UI thread. |cookies| is any Google 41 // Wallet cookies. 42 void GetGoogleCookiesCallback( 43 const base::Callback<void(const std::string&)>& callback, 44 const net::CookieList& cookies) { 45 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 46 47 std::string wallet_cookie; 48 for (size_t i = 0; i < cookies.size(); ++i) { 49 if (LowerCaseEqualsASCII(cookies[i].Name(), kWalletCookieName)) { 50 wallet_cookie = cookies[i].Value(); 51 break; 52 } 53 } 54 content::BrowserThread::PostTask(content::BrowserThread::UI, 55 FROM_HERE, 56 base::Bind(callback, wallet_cookie)); 57 } 58 59 // Gets Google Wallet cookies. Must be called on the IO thread. 60 // |request_context_getter| is a getter for the current request context. 61 // |callback| is called when retrieving cookies is completed. 62 void GetGoogleCookies( 63 scoped_refptr<net::URLRequestContextGetter> request_context_getter, 64 const base::Callback<void(const std::string&)>& callback) { 65 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::IO)); 66 67 net::URLRequestContext* url_request_context = 68 request_context_getter->GetURLRequestContext(); 69 if (!url_request_context) { 70 content::BrowserThread::PostTask(content::BrowserThread::UI, 71 FROM_HERE, 72 base::Bind(callback, std::string())); 73 return; 74 } 75 76 net::CookieStore* cookie_store = url_request_context->cookie_store(); 77 if (!cookie_store) { 78 content::BrowserThread::PostTask(content::BrowserThread::UI, 79 FROM_HERE, 80 base::Bind(callback, std::string())); 81 return; 82 } 83 84 net::CookieMonster* cookie_monster = cookie_store->GetCookieMonster(); 85 if (!cookie_monster) { 86 content::BrowserThread::PostTask(content::BrowserThread::UI, 87 FROM_HERE, 88 base::Bind(callback, std::string())); 89 return; 90 } 91 92 net::CookieOptions cookie_options; 93 cookie_options.set_include_httponly(); 94 cookie_monster->GetAllCookiesForURLWithOptionsAsync( 95 wallet::GetPassiveAuthUrl().GetWithEmptyPath(), 96 cookie_options, 97 base::Bind(&GetGoogleCookiesCallback, callback)); 98 } 99 100 } // namespace 101 102 WalletSigninHelper::WalletSigninHelper( 103 WalletSigninHelperDelegate* delegate, 104 net::URLRequestContextGetter* getter) 105 : delegate_(delegate), 106 getter_(getter), 107 state_(IDLE), 108 weak_ptr_factory_(this) { 109 DCHECK(delegate_); 110 } 111 112 WalletSigninHelper::~WalletSigninHelper() { 113 } 114 115 void WalletSigninHelper::StartPassiveSignin() { 116 DCHECK_EQ(IDLE, state_); 117 DCHECK(!url_fetcher_); 118 119 state_ = PASSIVE_EXECUTING_SIGNIN; 120 username_.clear(); 121 const GURL& url = wallet::GetPassiveAuthUrl(); 122 url_fetcher_.reset(net::URLFetcher::Create( 123 0, url, net::URLFetcher::GET, this)); 124 url_fetcher_->SetRequestContext(getter_); 125 url_fetcher_->Start(); 126 } 127 128 void WalletSigninHelper::StartUserNameFetch() { 129 DCHECK_EQ(state_, IDLE); 130 DCHECK(!url_fetcher_); 131 132 state_ = USERNAME_FETCHING_USERINFO; 133 username_.clear(); 134 StartFetchingUserNameFromSession(); 135 } 136 137 void WalletSigninHelper::StartWalletCookieValueFetch() { 138 scoped_refptr<net::URLRequestContextGetter> request_context(getter_); 139 if (!request_context.get()) { 140 ReturnWalletCookieValue(std::string()); 141 return; 142 } 143 144 base::Callback<void(const std::string&)> callback = base::Bind( 145 &WalletSigninHelper::ReturnWalletCookieValue, 146 weak_ptr_factory_.GetWeakPtr()); 147 148 base::Closure task = base::Bind(&GetGoogleCookies, request_context, callback); 149 content::BrowserThread::PostTask(content::BrowserThread::IO, FROM_HERE, task); 150 } 151 152 std::string WalletSigninHelper::GetGetAccountInfoUrlForTesting() const { 153 return base::StringPrintf(kGetAccountInfoUrlFormat, 0); 154 } 155 156 void WalletSigninHelper::OnServiceError(const GoogleServiceAuthError& error) { 157 const State state_with_error = state_; 158 state_ = IDLE; 159 url_fetcher_.reset(); 160 161 switch(state_with_error) { 162 case IDLE: 163 NOTREACHED(); 164 break; 165 166 case PASSIVE_EXECUTING_SIGNIN: /*FALLTHROUGH*/ 167 case PASSIVE_FETCHING_USERINFO: 168 delegate_->OnPassiveSigninFailure(error); 169 break; 170 171 case USERNAME_FETCHING_USERINFO: 172 delegate_->OnUserNameFetchFailure(error); 173 break; 174 } 175 } 176 177 void WalletSigninHelper::OnOtherError() { 178 OnServiceError(GoogleServiceAuthError::AuthErrorNone()); 179 } 180 181 void WalletSigninHelper::OnURLFetchComplete( 182 const net::URLFetcher* fetcher) { 183 DCHECK_EQ(url_fetcher_.get(), fetcher); 184 if (!fetcher->GetStatus().is_success() || 185 fetcher->GetResponseCode() < 200 || 186 fetcher->GetResponseCode() >= 300) { 187 LOG(ERROR) << "URLFetchFailure: state=" << state_ 188 << " r=" << fetcher->GetResponseCode() 189 << " s=" << fetcher->GetStatus().status() 190 << " e=" << fetcher->GetStatus().error(); 191 OnOtherError(); 192 return; 193 } 194 195 switch (state_) { 196 case USERNAME_FETCHING_USERINFO: /*FALLTHROUGH*/ 197 case PASSIVE_FETCHING_USERINFO: 198 ProcessGetAccountInfoResponseAndFinish(); 199 break; 200 201 case PASSIVE_EXECUTING_SIGNIN: 202 if (ParseSignInResponse()) { 203 url_fetcher_.reset(); 204 state_ = PASSIVE_FETCHING_USERINFO; 205 StartFetchingUserNameFromSession(); 206 } 207 break; 208 209 default: 210 NOTREACHED() << "unexpected state_=" << state_; 211 } 212 } 213 214 void WalletSigninHelper::StartFetchingUserNameFromSession() { 215 const int random_number = static_cast<int>(base::RandUint64() % INT_MAX); 216 url_fetcher_.reset( 217 net::URLFetcher::Create( 218 0, 219 GURL(base::StringPrintf(kGetAccountInfoUrlFormat, random_number)), 220 net::URLFetcher::GET, 221 this)); 222 url_fetcher_->SetRequestContext(getter_); 223 url_fetcher_->Start(); // This will result in OnURLFetchComplete callback. 224 } 225 226 void WalletSigninHelper::ProcessGetAccountInfoResponseAndFinish() { 227 std::string email; 228 if (!ParseGetAccountInfoResponse(url_fetcher_.get(), &email)) { 229 LOG(ERROR) << "failed to get the user email"; 230 OnOtherError(); 231 return; 232 } 233 234 username_ = email; 235 const State finishing_state = state_; 236 state_ = IDLE; 237 url_fetcher_.reset(); 238 switch(finishing_state) { 239 case USERNAME_FETCHING_USERINFO: 240 delegate_->OnUserNameFetchSuccess(username_); 241 break; 242 243 case PASSIVE_FETCHING_USERINFO: 244 delegate_->OnPassiveSigninSuccess(username_); 245 break; 246 247 default: 248 NOTREACHED() << "unexpected state_=" << finishing_state; 249 } 250 } 251 252 bool WalletSigninHelper::ParseSignInResponse() { 253 if (!url_fetcher_) { 254 NOTREACHED(); 255 return false; 256 } 257 258 std::string data; 259 if (!url_fetcher_->GetResponseAsString(&data)) { 260 DVLOG(1) << "failed to GetResponseAsString"; 261 OnOtherError(); 262 return false; 263 } 264 265 if (!LowerCaseEqualsASCII(data, "yes")) { 266 OnServiceError( 267 GoogleServiceAuthError(GoogleServiceAuthError::USER_NOT_SIGNED_UP)); 268 return false; 269 } 270 271 return true; 272 } 273 274 bool WalletSigninHelper::ParseGetAccountInfoResponse( 275 const net::URLFetcher* fetcher, std::string* email) { 276 DCHECK(email); 277 278 std::string data; 279 if (!fetcher->GetResponseAsString(&data)) { 280 DVLOG(1) << "failed to GetResponseAsString"; 281 return false; 282 } 283 284 scoped_ptr<base::Value> value(base::JSONReader::Read(data)); 285 if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY) { 286 DVLOG(1) << "failed to parse JSON response"; 287 return false; 288 } 289 290 DictionaryValue* dict = static_cast<base::DictionaryValue*>(value.get()); 291 base::ListValue* user_info; 292 if (!dict->GetListWithoutPathExpansion("user_info", &user_info)) { 293 DVLOG(1) << "no user_info in JSON response"; 294 return false; 295 } 296 297 // |user_info| will contain each signed in user in the cookie jar. 298 // We only support the first user at the moment. http://crbug.com/259543 299 // will change that. 300 base::DictionaryValue* user_info_detail; 301 if (!user_info->GetDictionary(0, &user_info_detail)) { 302 DVLOG(1) << "empty list in JSON response"; 303 return false; 304 } 305 306 if (!user_info_detail->GetStringWithoutPathExpansion("email", email)) { 307 DVLOG(1) << "no email in JSON response"; 308 return false; 309 } 310 311 return !email->empty(); 312 } 313 314 void WalletSigninHelper::ReturnWalletCookieValue( 315 const std::string& cookie_value) { 316 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI)); 317 318 delegate_->OnDidFetchWalletCookieValue(cookie_value); 319 } 320 321 } // namespace wallet 322 } // namespace autofill 323