710c3a22a8e852e6e3ff320977952b6d79dcdf4b
[platform/framework/web/crosswalk.git] / src / remoting / host / signaling_connector.cc
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
16 namespace remoting {
17
18 namespace {
19
20 // The delay between reconnect attempts will increase exponentially up
21 // to the maximum specified here.
22 const int kMaxReconnectDelaySeconds = 10 * 60;
23
24 // Time when we we try to update OAuth token before its expiration.
25 const int kTokenUpdateTimeBeforeExpirySeconds = 60;
26
27 }  // namespace
28
29 SignalingConnector::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
38 SignalingConnector::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
57 SignalingConnector::~SignalingConnector() {
58   signal_strategy_->RemoveListener(this);
59   net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this);
60   net::NetworkChangeNotifier::RemoveIPAddressObserver(this);
61 }
62
63 void 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
70 void 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
91 bool SignalingConnector::OnSignalStrategyIncomingStanza(
92     const buzz::XmlElement* stanza) {
93   return false;
94 }
95
96 void 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
106 void SignalingConnector::OnIPAddressChanged() {
107   DCHECK(CalledOnValidThread());
108   if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) {
109     HOST_LOG << "IP address has changed.";
110     ResetAndTryReconnect();
111   }
112 }
113
114 void SignalingConnector::OnGetTokensResponse(const std::string& user_email,
115                                              const std::string& access_token,
116                                              int expires_seconds) {
117   NOTREACHED();
118 }
119
120 void 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
135 void 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
157 void 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
165 void 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
174 void 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
184 void SignalingConnector::ResetAndTryReconnect() {
185   DCHECK(CalledOnValidThread());
186   signal_strategy_->Disconnect();
187   reconnect_attempts_ = 0;
188   timer_.Stop();
189   ScheduleTryReconnect();
190 }
191
192 void 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
203 void 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
230 void 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