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