| // Copyright (c) 2012 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "remoting/host/signaling_connector.h" |
| |
| #include "base/bind.h" |
| #include "base/callback.h" |
| #include "base/strings/string_util.h" |
| #include "google_apis/google_api_keys.h" |
| #include "net/url_request/url_fetcher.h" |
| #include "net/url_request/url_request_context_getter.h" |
| #include "remoting/host/dns_blackhole_checker.h" |
| |
| namespace remoting { |
| |
| namespace { |
| |
| // The delay between reconnect attempts will increase exponentially up |
| // to the maximum specified here. |
| const int kMaxReconnectDelaySeconds = 10 * 60; |
| |
| // Time when we we try to update OAuth token before its expiration. |
| const int kTokenUpdateTimeBeforeExpirySeconds = 60; |
| |
| } // namespace |
| |
| SignalingConnector::OAuthCredentials::OAuthCredentials( |
| const std::string& login_value, |
| const std::string& refresh_token_value, |
| bool is_service_account) |
| : login(login_value), |
| refresh_token(refresh_token_value), |
| is_service_account(is_service_account) { |
| } |
| |
| SignalingConnector::SignalingConnector( |
| XmppSignalStrategy* signal_strategy, |
| scoped_refptr<net::URLRequestContextGetter> url_request_context_getter, |
| scoped_ptr<DnsBlackholeChecker> dns_blackhole_checker, |
| const base::Closure& auth_failed_callback) |
| : signal_strategy_(signal_strategy), |
| url_request_context_getter_(url_request_context_getter), |
| auth_failed_callback_(auth_failed_callback), |
| dns_blackhole_checker_(dns_blackhole_checker.Pass()), |
| reconnect_attempts_(0), |
| refreshing_oauth_token_(false) { |
| DCHECK(!auth_failed_callback_.is_null()); |
| DCHECK(dns_blackhole_checker_.get()); |
| net::NetworkChangeNotifier::AddConnectionTypeObserver(this); |
| net::NetworkChangeNotifier::AddIPAddressObserver(this); |
| signal_strategy_->AddListener(this); |
| ScheduleTryReconnect(); |
| } |
| |
| SignalingConnector::~SignalingConnector() { |
| signal_strategy_->RemoveListener(this); |
| net::NetworkChangeNotifier::RemoveConnectionTypeObserver(this); |
| net::NetworkChangeNotifier::RemoveIPAddressObserver(this); |
| } |
| |
| void SignalingConnector::EnableOAuth( |
| scoped_ptr<OAuthCredentials> oauth_credentials) { |
| oauth_credentials_ = oauth_credentials.Pass(); |
| gaia_oauth_client_.reset( |
| new gaia::GaiaOAuthClient(url_request_context_getter_.get())); |
| } |
| |
| void SignalingConnector::OnSignalStrategyStateChange( |
| SignalStrategy::State state) { |
| DCHECK(CalledOnValidThread()); |
| |
| if (state == SignalStrategy::CONNECTED) { |
| LOG(INFO) << "Signaling connected."; |
| reconnect_attempts_ = 0; |
| } else if (state == SignalStrategy::DISCONNECTED) { |
| LOG(INFO) << "Signaling disconnected."; |
| reconnect_attempts_++; |
| |
| // If authentication failed then we have an invalid OAuth token, |
| // inform the upper layer about it. |
| if (signal_strategy_->GetError() == SignalStrategy::AUTHENTICATION_FAILED) { |
| auth_failed_callback_.Run(); |
| } else { |
| ScheduleTryReconnect(); |
| } |
| } |
| } |
| |
| bool SignalingConnector::OnSignalStrategyIncomingStanza( |
| const buzz::XmlElement* stanza) { |
| return false; |
| } |
| |
| void SignalingConnector::OnConnectionTypeChanged( |
| net::NetworkChangeNotifier::ConnectionType type) { |
| DCHECK(CalledOnValidThread()); |
| if (type != net::NetworkChangeNotifier::CONNECTION_NONE && |
| signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) { |
| LOG(INFO) << "Network state changed to online."; |
| ResetAndTryReconnect(); |
| } |
| } |
| |
| void SignalingConnector::OnIPAddressChanged() { |
| DCHECK(CalledOnValidThread()); |
| if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) { |
| LOG(INFO) << "IP address has changed."; |
| ResetAndTryReconnect(); |
| } |
| } |
| |
| void SignalingConnector::OnGetTokensResponse(const std::string& user_email, |
| const std::string& access_token, |
| int expires_seconds) { |
| NOTREACHED(); |
| } |
| |
| void SignalingConnector::OnRefreshTokenResponse( |
| const std::string& access_token, |
| int expires_seconds) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(oauth_credentials_.get()); |
| LOG(INFO) << "Received OAuth token."; |
| |
| oauth_access_token_ = access_token; |
| auth_token_expiry_time_ = base::Time::Now() + |
| base::TimeDelta::FromSeconds(expires_seconds) - |
| base::TimeDelta::FromSeconds(kTokenUpdateTimeBeforeExpirySeconds); |
| |
| gaia_oauth_client_->GetUserEmail(access_token, 1, this); |
| } |
| |
| void SignalingConnector::OnGetUserEmailResponse(const std::string& user_email) { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(oauth_credentials_.get()); |
| LOG(INFO) << "Received user info."; |
| |
| if (user_email != oauth_credentials_->login) { |
| LOG(ERROR) << "OAuth token and email address do not refer to " |
| "the same account."; |
| auth_failed_callback_.Run(); |
| return; |
| } |
| |
| signal_strategy_->SetAuthInfo(oauth_credentials_->login, |
| oauth_access_token_, "oauth2"); |
| refreshing_oauth_token_ = false; |
| |
| // Now that we've refreshed the token and verified that it's for the correct |
| // user account, try to connect using the new token. |
| DCHECK_EQ(signal_strategy_->GetState(), SignalStrategy::DISCONNECTED); |
| signal_strategy_->Connect(); |
| } |
| |
| void SignalingConnector::OnOAuthError() { |
| DCHECK(CalledOnValidThread()); |
| LOG(ERROR) << "OAuth: invalid credentials."; |
| refreshing_oauth_token_ = false; |
| reconnect_attempts_++; |
| auth_failed_callback_.Run(); |
| } |
| |
| void SignalingConnector::OnNetworkError(int response_code) { |
| DCHECK(CalledOnValidThread()); |
| LOG(ERROR) << "Network error when trying to update OAuth token: " |
| << response_code; |
| refreshing_oauth_token_ = false; |
| reconnect_attempts_++; |
| ScheduleTryReconnect(); |
| } |
| |
| void SignalingConnector::ScheduleTryReconnect() { |
| DCHECK(CalledOnValidThread()); |
| if (timer_.IsRunning() || net::NetworkChangeNotifier::IsOffline()) |
| return; |
| int delay_s = std::min(1 << reconnect_attempts_, |
| kMaxReconnectDelaySeconds); |
| timer_.Start(FROM_HERE, base::TimeDelta::FromSeconds(delay_s), |
| this, &SignalingConnector::TryReconnect); |
| } |
| |
| void SignalingConnector::ResetAndTryReconnect() { |
| DCHECK(CalledOnValidThread()); |
| signal_strategy_->Disconnect(); |
| reconnect_attempts_ = 0; |
| timer_.Stop(); |
| ScheduleTryReconnect(); |
| } |
| |
| void SignalingConnector::TryReconnect() { |
| DCHECK(CalledOnValidThread()); |
| DCHECK(dns_blackhole_checker_.get()); |
| |
| // This will check if this machine is allowed to access the chromoting |
| // host talkgadget. |
| dns_blackhole_checker_->CheckForDnsBlackhole( |
| base::Bind(&SignalingConnector::OnDnsBlackholeCheckerDone, |
| base::Unretained(this))); |
| } |
| |
| void SignalingConnector::OnDnsBlackholeCheckerDone(bool allow) { |
| DCHECK(CalledOnValidThread()); |
| |
| // Unable to access the host talkgadget. Don't allow the connection, but |
| // schedule a reconnect in case this is a transient problem rather than |
| // an outright block. |
| if (!allow) { |
| reconnect_attempts_++; |
| LOG(INFO) << "Talkgadget check failed. Scheduling reconnect. Attempt " |
| << reconnect_attempts_; |
| ScheduleTryReconnect(); |
| return; |
| } |
| |
| if (signal_strategy_->GetState() == SignalStrategy::DISCONNECTED) { |
| bool need_new_auth_token = oauth_credentials_.get() && |
| (auth_token_expiry_time_.is_null() || |
| base::Time::Now() >= auth_token_expiry_time_); |
| if (need_new_auth_token) { |
| RefreshOAuthToken(); |
| } else { |
| LOG(INFO) << "Attempting to connect signaling."; |
| signal_strategy_->Connect(); |
| } |
| } |
| } |
| |
| void SignalingConnector::RefreshOAuthToken() { |
| DCHECK(CalledOnValidThread()); |
| LOG(INFO) << "Refreshing OAuth token."; |
| DCHECK(!refreshing_oauth_token_); |
| |
| // Service accounts use different API keys, as they use the client app flow. |
| google_apis::OAuth2Client oauth2_client; |
| if (oauth_credentials_->is_service_account) { |
| oauth2_client = google_apis::CLIENT_REMOTING_HOST; |
| } else { |
| oauth2_client = google_apis::CLIENT_REMOTING; |
| } |
| |
| gaia::OAuthClientInfo client_info = { |
| google_apis::GetOAuth2ClientID(oauth2_client), |
| google_apis::GetOAuth2ClientSecret(oauth2_client), |
| // Redirect URL is only used when getting tokens from auth code. It |
| // is not required when getting access tokens. |
| "" |
| }; |
| |
| refreshing_oauth_token_ = true; |
| std::vector<std::string> empty_scope_list; // (Use scope from refresh token.) |
| gaia_oauth_client_->RefreshToken( |
| client_info, oauth_credentials_->refresh_token, empty_scope_list, |
| 1, this); |
| } |
| |
| } // namespace remoting |