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 "remoting/host/signaling_connector.h" 6 7#include "base/bind.h" 8#include "base/callback.h" 9#include "google_apis/google_api_keys.h" 10#include "net/url_request/url_fetcher.h" 11#include "net/url_request/url_request_context_getter.h" 12#include "remoting/host/dns_blackhole_checker.h" 13 14namespace remoting { 15 16namespace { 17 18// The delay between reconnect attempts will increase exponentially up 19// to the maximum specified here. 20const int kMaxReconnectDelaySeconds = 10 * 60; 21 22// Time when we we try to update OAuth token before its expiration. 23const int kTokenUpdateTimeBeforeExpirySeconds = 60; 24 25} // namespace 26 27SignalingConnector::OAuthCredentials::OAuthCredentials( 28 const std::string& login_value, 29 const std::string& refresh_token_value) 30 : login(login_value), 31 refresh_token(refresh_token_value) { 32} 33 34SignalingConnector::SignalingConnector( 35 XmppSignalStrategy* signal_strategy, 36 scoped_refptr<net::URLRequestContextGetter> url_request_context_getter, 37 scoped_ptr<DnsBlackholeChecker> dns_blackhole_checker, 38 const base::Closure& auth_failed_callback) 39 : signal_strategy_(signal_strategy), 40 url_request_context_getter_(url_request_context_getter), 41 auth_failed_callback_(auth_failed_callback), 42 dns_blackhole_checker_(dns_blackhole_checker.Pass()), 43 reconnect_attempts_(0), 44 refreshing_oauth_token_(false) { 45 DCHECK(!auth_failed_callback_.is_null()); 46 DCHECK(dns_blackhole_checker_.get()); 47 net::NetworkChangeNotifier::AddConnectionTypeObserver(this); 48 net::NetworkChangeNotifier::AddIPAddressObserver(this); 49 signal_strategy_->AddListener(this); 50 ScheduleTryReconnect(); 51} 52 53SignalingConnector::~SignalingConnector() { 54 signal_strategy_->RemoveListener(this); 55 net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); 56 net::NetworkChangeNotifier::RemoveIPAddressObserver(this); 57} 58 59void SignalingConnector::EnableOAuth( 60 scoped_ptr<OAuthCredentials> oauth_credentials) { 61 oauth_credentials_ = oauth_credentials.Pass(); 62 gaia_oauth_client_.reset( 63 new gaia::GaiaOAuthClient(url_request_context_getter_.get())); 64} 65 66void SignalingConnector::OnSignalStrategyStateChange( 67 SignalStrategy::State state) { 68 DCHECK(CalledOnValidThread()); 69 70 if (state == SignalStrategy::CONNECTED) { 71 LOG(INFO) << "Signaling connected."; 72 reconnect_attempts_ = 0; 73 } else if (state == SignalStrategy::DISCONNECTED) { 74 LOG(INFO) << "Signaling disconnected."; 75 reconnect_attempts_++; 76 77 // If authentication failed then we have an invalid OAuth token, 78 // inform the upper layer about it. 79 if (signal_strategy_->GetError() == SignalStrategy::AUTHENTICATION_FAILED) { 80 auth_failed_callback_.Run(); 81 } else { 82 ScheduleTryReconnect(); 83 } 84 } 85} 86 87bool SignalingConnector::OnSignalStrategyIncomingStanza( 88 const buzz::XmlElement* stanza) { 89 return false; 90} 91 92void SignalingConnector::OnConnectionTypeChanged( 93 net::NetworkChangeNotifier::ConnectionType type) { 94 DCHECK(CalledOnValidThread()); 95 if (type != net::NetworkChangeNotifier::CONNECTION_NONE && 96 signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) { 97 LOG(INFO) << "Network state changed to online."; 98 ResetAndTryReconnect(); 99 } 100} 101 102void SignalingConnector::OnIPAddressChanged() { 103 DCHECK(CalledOnValidThread()); 104 if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) { 105 LOG(INFO) << "IP address has changed."; 106 ResetAndTryReconnect(); 107 } 108} 109 110void SignalingConnector::OnGetTokensResponse(const std::string& user_email, 111 const std::string& access_token, 112 int expires_seconds) { 113 NOTREACHED(); 114} 115 116void SignalingConnector::OnRefreshTokenResponse( 117 const std::string& access_token, 118 int expires_seconds) { 119 DCHECK(CalledOnValidThread()); 120 DCHECK(oauth_credentials_.get()); 121 LOG(INFO) << "Received OAuth token."; 122 123 oauth_access_token_ = access_token; 124 auth_token_expiry_time_ = base::Time::Now() + 125 base::TimeDelta::FromSeconds(expires_seconds) - 126 base::TimeDelta::FromSeconds(kTokenUpdateTimeBeforeExpirySeconds); 127 128 gaia_oauth_client_->GetUserEmail(access_token, 1, this); 129} 130 131void SignalingConnector::OnGetUserEmailResponse(const std::string& user_email) { 132 DCHECK(CalledOnValidThread()); 133 DCHECK(oauth_credentials_.get()); 134 LOG(INFO) << "Received user info."; 135 136 if (user_email != oauth_credentials_->login) { 137 LOG(ERROR) << "OAuth token and email address do not refer to " 138 "the same account."; 139 auth_failed_callback_.Run(); 140 return; 141 } 142 143 signal_strategy_->SetAuthInfo(oauth_credentials_->login, 144 oauth_access_token_, "oauth2"); 145 refreshing_oauth_token_ = false; 146 147 // Now that we've refreshed the token and verified that it's for the correct 148 // user account, try to connect using the new token. 149 DCHECK_EQ(signal_strategy_->GetState(), SignalStrategy::DISCONNECTED); 150 signal_strategy_->Connect(); 151} 152 153void SignalingConnector::OnOAuthError() { 154 DCHECK(CalledOnValidThread()); 155 LOG(ERROR) << "OAuth: invalid credentials."; 156 refreshing_oauth_token_ = false; 157 reconnect_attempts_++; 158 auth_failed_callback_.Run(); 159} 160 161void SignalingConnector::OnNetworkError(int response_code) { 162 DCHECK(CalledOnValidThread()); 163 LOG(ERROR) << "Network error when trying to update OAuth token: " 164 << response_code; 165 refreshing_oauth_token_ = false; 166 reconnect_attempts_++; 167 ScheduleTryReconnect(); 168} 169 170void SignalingConnector::ScheduleTryReconnect() { 171 DCHECK(CalledOnValidThread()); 172 if (timer_.IsRunning() || net::NetworkChangeNotifier::IsOffline()) 173 return; 174 int delay_s = std::min(1 << reconnect_attempts_, 175 kMaxReconnectDelaySeconds); 176 timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(delay_s), 177 this, &SignalingConnector::TryReconnect); 178} 179 180void SignalingConnector::ResetAndTryReconnect() { 181 DCHECK(CalledOnValidThread()); 182 signal_strategy_->Disconnect(); 183 reconnect_attempts_ = 0; 184 timer_.Stop(); 185 ScheduleTryReconnect(); 186} 187 188void SignalingConnector::TryReconnect() { 189 DCHECK(CalledOnValidThread()); 190 DCHECK(dns_blackhole_checker_.get()); 191 192 // This will check if this machine is allowed to access the chromoting 193 // host talkgadget. 194 dns_blackhole_checker_->CheckForDnsBlackhole( 195 base::Bind(&SignalingConnector::OnDnsBlackholeCheckerDone, 196 base::Unretained(this))); 197} 198 199void SignalingConnector::OnDnsBlackholeCheckerDone(bool allow) { 200 DCHECK(CalledOnValidThread()); 201 202 // Unable to access the host talkgadget. Don't allow the connection, but 203 // schedule a reconnect in case this is a transient problem rather than 204 // an outright block. 205 if (!allow) { 206 reconnect_attempts_++; 207 LOG(INFO) << "Talkgadget check failed. Scheduling reconnect. Attempt " 208 << reconnect_attempts_; 209 ScheduleTryReconnect(); 210 return; 211 } 212 213 if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) { 214 bool need_new_auth_token = oauth_credentials_.get() && 215 (auth_token_expiry_time_.is_null() || 216 base::Time::Now() >= auth_token_expiry_time_); 217 if (need_new_auth_token) { 218 RefreshOAuthToken(); 219 } else { 220 LOG(INFO) << "Attempting to connect signaling."; 221 signal_strategy_->Connect(); 222 } 223 } 224} 225 226void SignalingConnector::RefreshOAuthToken() { 227 DCHECK(CalledOnValidThread()); 228 LOG(INFO) << "Refreshing OAuth token."; 229 DCHECK(!refreshing_oauth_token_); 230 231 gaia::OAuthClientInfo client_info = { 232 google_apis::GetOAuth2ClientID(google_apis::CLIENT_REMOTING), 233 google_apis::GetOAuth2ClientSecret(google_apis::CLIENT_REMOTING), 234 // Redirect URL is only used when getting tokens from auth code. It 235 // is not required when getting access tokens. 236 "" 237 }; 238 239 refreshing_oauth_token_ = true; 240 std::vector<std::string> empty_scope_list; // (Use scope from refresh token.) 241 gaia_oauth_client_->RefreshToken( 242 client_info, oauth_credentials_->refresh_token, empty_scope_list, 243 1, this); 244} 245 246} // namespace remoting 247