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