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