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