15821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Copyright (c) 2012 The Chromium Authors. All rights reserved. 25821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// Use of this source code is governed by a BSD-style license that can be 35821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// found in the LICENSE file. 45821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 55821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "google_apis/gaia/gaia_oauth_client.h" 65821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 75821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/json/json_reader.h" 85821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/logging.h" 95821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/memory/scoped_ptr.h" 10eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch#include "base/strings/string_util.h" 115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "base/values.h" 125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "google_apis/gaia/gaia_urls.h" 135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/base/escape.h" 145d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles)#include "net/base/load_flags.h" 155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/http/http_status_code.h" 165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/url_request/url_fetcher.h" 175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/url_request/url_fetcher_delegate.h" 185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)#include "net/url_request/url_request_context_getter.h" 197dbb3d5cf0c15f500944d211057644d6a2f37371Ben Murdoch#include "url/gurl.h" 205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace { 225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kAccessTokenValue[] = "access_token"; 235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kRefreshTokenValue[] = "refresh_token"; 245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)const char kExpiresInValue[] = "expires_in"; 255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)namespace gaia { 285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 29eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// Use a non-zero number, so unit tests can differentiate the URLFetcher used by 30eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch// this class from other fetchers (most other code just hardcodes the ID to 0). 31eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochconst int GaiaOAuthClient::kUrlFetcherId = 17109006; 32eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)class GaiaOAuthClient::Core 345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) : public base::RefCountedThreadSafe<GaiaOAuthClient::Core>, 355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public net::URLFetcherDelegate { 365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) public: 37eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch Core(net::URLRequestContextGetter* request_context_getter) 38eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch : num_retries_(0), 39eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch request_context_getter_(request_context_getter), 40eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch delegate_(NULL), 41eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch request_type_(NO_PENDING_REQUEST) { 425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void GetTokensFromAuthCode(const OAuthClientInfo& oauth_client_info, 455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& auth_code, 465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate); 485821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void RefreshToken(const OAuthClientInfo& oauth_client_info, 495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& refresh_token, 50eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const std::vector<std::string>& scopes, 515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate); 53eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch void GetUserEmail(const std::string& oauth_access_token, 54eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch int max_retries, 55eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch Delegate* delegate); 568bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) void GetUserId(const std::string& oauth_access_token, 578bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) int max_retries, 588bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Delegate* delegate); 596e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) void GetUserInfo(const std::string& oauth_access_token, 606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) int max_retries, 616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) Delegate* delegate); 62eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch void GetTokenInfo(const std::string& oauth_access_token, 635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Delegate* delegate); 655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // net::URLFetcherDelegate implementation. 675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual void OnURLFetchComplete(const net::URLFetcher* source) OVERRIDE; 685821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) private: 705821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) friend class base::RefCountedThreadSafe<Core>; 715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) enum RequestType { 735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NO_PENDING_REQUEST, 745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) TOKENS_FROM_AUTH_CODE, 755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) REFRESH_TOKEN, 76eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch TOKEN_INFO, 778bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) USER_EMAIL, 788bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) USER_ID, 796e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) USER_INFO, 805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) }; 815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) virtual ~Core() {} 835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 846e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) void GetUserInfoImpl(RequestType type, 856e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) const std::string& oauth_access_token, 866e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) int max_retries, 876e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) Delegate* delegate); 88eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch void MakeGaiaRequest(const GURL& url, 89eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const std::string& post_body, 905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate); 925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) void HandleResponse(const net::URLFetcher* source, 935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool* should_retry_request); 945821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 955821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int num_retries_; 965821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_refptr<net::URLRequestContextGetter> request_context_getter_; 975821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate_; 985821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<net::URLFetcher> request_; 995821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) RequestType request_type_; 1005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)}; 1015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void GaiaOAuthClient::Core::GetTokensFromAuthCode( 1035821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const OAuthClientInfo& oauth_client_info, 1045821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& auth_code, 1055821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 1065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate) { 1075821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 1085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_type_ = TOKENS_FROM_AUTH_CODE; 1095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string post_body = 1105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "code=" + net::EscapeUrlEncodedData(auth_code, true) + 1115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id, 1125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) true) + 1135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&client_secret=" + 1145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) + 1155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&redirect_uri=" + 1165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) net::EscapeUrlEncodedData(oauth_client_info.redirect_uri, true) + 1175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&grant_type=authorization_code"; 118eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()), 119eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch post_body, max_retries, delegate); 1205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 1225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void GaiaOAuthClient::Core::RefreshToken( 1235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const OAuthClientInfo& oauth_client_info, 1245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& refresh_token, 125eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const std::vector<std::string>& scopes, 1265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 1275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate) { 1285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 1295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_type_ = REFRESH_TOKEN; 1305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string post_body = 1315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "refresh_token=" + net::EscapeUrlEncodedData(refresh_token, true) + 1325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&client_id=" + net::EscapeUrlEncodedData(oauth_client_info.client_id, 1335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) true) + 1345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&client_secret=" + 1355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) net::EscapeUrlEncodedData(oauth_client_info.client_secret, true) + 1365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) "&grant_type=refresh_token"; 137eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 138eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch if (!scopes.empty()) { 139eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch std::string scopes_string = JoinString(scopes, ' '); 140eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch post_body += "&scope=" + net::EscapeUrlEncodedData(scopes_string, true); 141eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 142eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 143eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_url()), 144eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch post_body, max_retries, delegate); 1455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 147eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid GaiaOAuthClient::Core::GetUserEmail(const std::string& oauth_access_token, 148eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch int max_retries, 149eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch Delegate* delegate) { 1506e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) GetUserInfoImpl(USER_EMAIL, oauth_access_token, max_retries, delegate); 1518bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)} 1528bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1538bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)void GaiaOAuthClient::Core::GetUserId(const std::string& oauth_access_token, 1548bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) int max_retries, 1558bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Delegate* delegate) { 1566e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) GetUserInfoImpl(USER_ID, oauth_access_token, max_retries, delegate); 1578bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)} 1588bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 1598bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)void GaiaOAuthClient::Core::GetUserInfo(const std::string& oauth_access_token, 1606e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) int max_retries, 1616e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) Delegate* delegate) { 1626e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) GetUserInfoImpl(USER_INFO, oauth_access_token, max_retries, delegate); 1636e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)} 1646e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 1656e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)void GaiaOAuthClient::Core::GetUserInfoImpl( 1666e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) RequestType type, 1676e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) const std::string& oauth_access_token, 1686e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) int max_retries, 1696e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) Delegate* delegate) { 1706e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 1716e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) DCHECK(!request_.get()); 1726e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) request_type_ = type; 1735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_ = delegate; 1745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) num_retries_ = 0; 1755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_.reset(net::URLFetcher::Create( 176116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch kUrlFetcherId, GURL(GaiaUrls::GetInstance()->oauth_user_info_url()), 1775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) net::URLFetcher::GET, this)); 178868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) request_->SetRequestContext(request_context_getter_.get()); 179868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) request_->AddExtraRequestHeader("Authorization: OAuth " + oauth_access_token); 1802a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) request_->SetMaxRetriesOn5xx(max_retries); 1815d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 1825d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) net::LOAD_DO_NOT_SAVE_COOKIES); 1835d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) 1842a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // Fetchers are sometimes cancelled because a network change was detected, 1852a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // especially at startup and after sign-in on ChromeOS. Retrying once should 1862a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // be enough in those cases; let the fetcher retry up to 3 times just in case. 1872a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // http://crbug.com/163710 1882a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) request_->SetAutomaticallyRetryOnNetworkChanges(3); 1895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_->Start(); 1905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 1915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 192eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid GaiaOAuthClient::Core::GetTokenInfo(const std::string& oauth_access_token, 193eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch int max_retries, 194eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch Delegate* delegate) { 195eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch DCHECK_EQ(request_type_, NO_PENDING_REQUEST); 196eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch DCHECK(!request_.get()); 197eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch request_type_ = TOKEN_INFO; 198eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch std::string post_body = 199eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch "access_token=" + net::EscapeUrlEncodedData(oauth_access_token, true); 200eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch MakeGaiaRequest(GURL(GaiaUrls::GetInstance()->oauth2_token_info_url()), 201eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch post_body, 202eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch max_retries, 203eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch delegate); 204eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 205eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 2065821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void GaiaOAuthClient::Core::MakeGaiaRequest( 207eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const GURL& url, 2085821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& post_body, 2095821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 2105821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) GaiaOAuthClient::Delegate* delegate) { 2115821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK(!request_.get()) << "Tried to fetch two things at once!"; 2125821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_ = delegate; 2135821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) num_retries_ = 0; 2145821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_.reset(net::URLFetcher::Create( 215eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch kUrlFetcherId, url, net::URLFetcher::POST, this)); 216868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) request_->SetRequestContext(request_context_getter_.get()); 2175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_->SetUploadData("application/x-www-form-urlencoded", post_body); 2182a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) request_->SetMaxRetriesOn5xx(max_retries); 2195d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) request_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | 2205d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) net::LOAD_DO_NOT_SAVE_COOKIES); 2212a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) // See comment on SetAutomaticallyRetryOnNetworkChanges() above. 2222a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) request_->SetAutomaticallyRetryOnNetworkChanges(3); 2235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_->Start(); 2245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 2255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)// URLFetcher::Delegate implementation. 2275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void GaiaOAuthClient::Core::OnURLFetchComplete( 2285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const net::URLFetcher* source) { 2295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool should_retry = false; 2305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) HandleResponse(source, &should_retry); 2315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (should_retry) { 2325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Explicitly call ReceivedContentWasMalformed() to ensure the current 2335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // request gets counted as a failure for calculation of the back-off 2345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // period. If it was already a failure by status code, this call will 2355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // be ignored. 2365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_->ReceivedContentWasMalformed(); 2375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) num_retries_++; 2385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // We must set our request_context_getter_ again because 2395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // URLFetcher::Core::RetryOrCompleteUrlFetch resets it to NULL... 240868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) request_->SetRequestContext(request_context_getter_.get()); 2415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_->Start(); 2425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 2445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void GaiaOAuthClient::Core::HandleResponse( 2465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const net::URLFetcher* source, 2475821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) bool* should_retry_request) { 248eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // Move ownership of the request fetcher into a local scoped_ptr which 249eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // will be nuked when we're done handling the request, unless we need 250eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch // to retry, in which case ownership will be returned to request_. 2515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) scoped_ptr<net::URLFetcher> old_request = request_.Pass(); 2525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) DCHECK_EQ(source, old_request.get()); 2535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2545d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // HTTP_BAD_REQUEST means the arguments are invalid. HTTP_UNAUTHORIZED means 2555d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) // the access or refresh token is invalid. No point retrying. We are 2565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // done here. 2575d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) int response_code = source->GetResponseCode(); 2585d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) if (response_code == net::HTTP_BAD_REQUEST || 2595d1f7b1de12d16ceb2c938c56701a3e8bfa558f7Torne (Richard Coles) response_code == net::HTTP_UNAUTHORIZED) { 2605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_->OnOAuthError(); 2615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 264eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch scoped_ptr<base::DictionaryValue> response_dict; 2655821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (source->GetResponseCode() == net::HTTP_OK) { 2665821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string data; 2675821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) source->GetResponseAsString(&data); 268eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch scoped_ptr<base::Value> message_value(base::JSONReader::Read(data)); 2695821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (message_value.get() && 270eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch message_value->IsType(base::Value::TYPE_DICTIONARY)) { 2715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) response_dict.reset( 272eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch static_cast<base::DictionaryValue*>(message_value.release())); 2735821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (!response_dict.get()) { 2775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // If we don't have an access token yet and the the error was not 2785821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // RC_BAD_REQUEST, we may need to retry. 2792a99a7e74a7f215066514fe81d2bfa6639d9edddTorne (Richard Coles) if ((source->GetMaxRetriesOn5xx() != -1) && 280868fa2fe829687343ffae624259930155e16dbd8Torne (Richard Coles) (num_retries_ >= source->GetMaxRetriesOn5xx())) { 2815821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) // Retry limit reached. Give up. 2825821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_->OnNetworkError(source->GetResponseCode()); 2835821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 2845821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_ = old_request.Pass(); 2855821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) *should_retry_request = true; 2865821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2875821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 2885821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 2895821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2905821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) RequestType type = request_type_; 2915821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) request_type_ = NO_PENDING_REQUEST; 2925821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 2935821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) switch (type) { 2948bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) case USER_EMAIL: { 295116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch std::string email; 296116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch response_dict->GetString("email", &email); 297116680a4aac90f2aa7413d9095a592090648e557Ben Murdoch delegate_->OnGetUserEmailResponse(email); 298eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch break; 299eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch } 300eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 3018bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) case USER_ID: { 3028bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) std::string id; 3038bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) response_dict->GetString("id", &id); 3048bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) delegate_->OnGetUserIdResponse(id); 3058bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) break; 3068bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) } 3078bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 3086e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) case USER_INFO: { 3096e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) delegate_->OnGetUserInfoResponse(response_dict.Pass()); 3106e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) break; 3116e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) } 3126e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 313eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch case TOKEN_INFO: { 314eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch delegate_->OnGetTokenInfoResponse(response_dict.Pass()); 3155821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 3165821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3175821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3185821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) case TOKENS_FROM_AUTH_CODE: 3195821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) case REFRESH_TOKEN: { 3205821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string access_token; 3215821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) std::string refresh_token; 3225821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int expires_in_seconds = 0; 3235821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) response_dict->GetString(kAccessTokenValue, &access_token); 3245821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) response_dict->GetString(kRefreshTokenValue, &refresh_token); 3255821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) response_dict->GetInteger(kExpiresInValue, &expires_in_seconds); 3265821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3275821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (access_token.empty()) { 3285821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_->OnOAuthError(); 3295821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return; 3305821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3315821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3325821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) if (type == REFRESH_TOKEN) { 3335821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_->OnRefreshTokenResponse(access_token, expires_in_seconds); 3345821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } else { 3355821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate_->OnGetTokensResponse(refresh_token, 3365821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) access_token, 3375821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) expires_in_seconds); 3385821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3395821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) break; 3405821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3415821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3425821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) default: 3435821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) NOTREACHED(); 3445821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) } 3455821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3465821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 347eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen MurdochGaiaOAuthClient::GaiaOAuthClient(net::URLRequestContextGetter* context_getter) { 348eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch core_ = new Core(context_getter); 3495821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3505821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3515821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)GaiaOAuthClient::~GaiaOAuthClient() { 3525821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3535821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 3545821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)void GaiaOAuthClient::GetTokensFromAuthCode( 3555821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const OAuthClientInfo& oauth_client_info, 3565821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) const std::string& auth_code, 3575821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 3585821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Delegate* delegate) { 3595821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return core_->GetTokensFromAuthCode(oauth_client_info, 3605821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) auth_code, 3615821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) max_retries, 3625821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate); 3635821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3645821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 365eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid GaiaOAuthClient::RefreshToken( 366eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const OAuthClientInfo& oauth_client_info, 367eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const std::string& refresh_token, 368eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch const std::vector<std::string>& scopes, 369eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch int max_retries, 370eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch Delegate* delegate) { 3715821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) return core_->RefreshToken(oauth_client_info, 3725821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) refresh_token, 373eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch scopes, 3745821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) max_retries, 3755821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) delegate); 3765821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 3775821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 378eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid GaiaOAuthClient::GetUserEmail(const std::string& access_token, 3795821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) int max_retries, 3805821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) Delegate* delegate) { 381eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return core_->GetUserEmail(access_token, max_retries, delegate); 382eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch} 383eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch 3848bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)void GaiaOAuthClient::GetUserId(const std::string& access_token, 3858bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) int max_retries, 3868bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) Delegate* delegate) { 3878bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) return core_->GetUserId(access_token, max_retries, delegate); 3888bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles)} 3898bcbed890bc3ce4d7a057a8f32cab53fa534672eTorne (Richard Coles) 3906e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)void GaiaOAuthClient::GetUserInfo(const std::string& access_token, 3916e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) int max_retries, 3926e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) Delegate* delegate) { 3936e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) return core_->GetUserInfo(access_token, max_retries, delegate); 3946e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles)} 3956e8cce623b6e4fe0c9e4af605d675dd9d0338c38Torne (Richard Coles) 396eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdochvoid GaiaOAuthClient::GetTokenInfo(const std::string& access_token, 397eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch int max_retries, 398eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch Delegate* delegate) { 399eb525c5499e34cc9c4b825d6d9e75bb07cc06aceBen Murdoch return core_->GetTokenInfo(access_token, max_retries, delegate); 4005821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} 4015821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles) 4025821806d5e7f356e8fa4b058a389a808ea183019Torne (Richard Coles)} // namespace gaia 403