jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 1 | // Copyright 2014 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 | |
nicholss | f620a47c | 2017-02-02 23:39:08 | [diff] [blame] | 5 | #include "remoting/base/oauth_token_getter_impl.h" |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 6 | |
sergeyu | 1417e013 | 2015-12-23 19:01:22 | [diff] [blame] | 7 | #include <utility> |
| 8 | |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 9 | #include "base/bind.h" |
| 10 | #include "base/callback.h" |
| 11 | #include "base/strings/string_util.h" |
| 12 | #include "google_apis/google_api_keys.h" |
| 13 | #include "net/url_request/url_request_context_getter.h" |
| 14 | #include "remoting/base/logging.h" |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 15 | #include "remoting/base/oauth_helper.h" |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 16 | |
| 17 | namespace remoting { |
| 18 | |
| 19 | namespace { |
| 20 | |
| 21 | // Maximum number of retries on network/500 errors. |
| 22 | const int kMaxRetries = 3; |
| 23 | |
| 24 | // Time when we we try to update OAuth token before its expiration. |
| 25 | const int kTokenUpdateTimeBeforeExpirySeconds = 60; |
| 26 | |
| 27 | } // namespace |
| 28 | |
| 29 | OAuthTokenGetterImpl::OAuthTokenGetterImpl( |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 30 | std::unique_ptr<OAuthIntermediateCredentials> intermediate_credentials, |
| 31 | const OAuthTokenGetter::CredentialsUpdatedCallback& on_credentials_update, |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 32 | const scoped_refptr<net::URLRequestContextGetter>& |
| 33 | url_request_context_getter, |
jrw | 5b3e1c46 | 2015-07-17 02:52:39 | [diff] [blame] | 34 | bool auto_refresh) |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 35 | : intermediate_credentials_(std::move(intermediate_credentials)), |
| 36 | gaia_oauth_client_( |
| 37 | new gaia::GaiaOAuthClient(url_request_context_getter.get())), |
| 38 | credentials_updated_callback_(on_credentials_update), |
| 39 | url_request_context_getter_(url_request_context_getter) { |
| 40 | if (auto_refresh) { |
| 41 | refresh_timer_.reset(new base::OneShotTimer()); |
| 42 | } |
| 43 | } |
| 44 | |
| 45 | OAuthTokenGetterImpl::OAuthTokenGetterImpl( |
| 46 | std::unique_ptr<OAuthAuthorizationCredentials> authorization_credentials, |
| 47 | const scoped_refptr<net::URLRequestContextGetter>& |
| 48 | url_request_context_getter, |
| 49 | bool auto_refresh) |
| 50 | : authorization_credentials_(std::move(authorization_credentials)), |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 51 | gaia_oauth_client_( |
| 52 | new gaia::GaiaOAuthClient(url_request_context_getter.get())), |
jrw | 5b3e1c46 | 2015-07-17 02:52:39 | [diff] [blame] | 53 | url_request_context_getter_(url_request_context_getter) { |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 54 | if (auto_refresh) { |
danakj | 8c3eb80 | 2015-09-24 07:53:00 | [diff] [blame] | 55 | refresh_timer_.reset(new base::OneShotTimer()); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 56 | } |
| 57 | } |
| 58 | |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 59 | OAuthTokenGetterImpl::~OAuthTokenGetterImpl() { |
| 60 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
| 61 | } |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 62 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 63 | void OAuthTokenGetterImpl::OnGetTokensResponse(const std::string& refresh_token, |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 64 | const std::string& access_token, |
| 65 | int expires_seconds) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 66 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 67 | DCHECK(intermediate_credentials_); |
| 68 | VLOG(1) << "Received OAuth tokens."; |
| 69 | |
| 70 | // Update the access token and any other auto-update timers. |
| 71 | UpdateAccessToken(access_token, expires_seconds); |
| 72 | |
| 73 | // Keep the refresh token in the authorization_credentials. |
| 74 | authorization_credentials_.reset( |
| 75 | new OAuthTokenGetter::OAuthAuthorizationCredentials( |
| 76 | std::string(), refresh_token, |
| 77 | intermediate_credentials_->is_service_account)); |
| 78 | |
| 79 | // Clear out the one time use token. |
| 80 | intermediate_credentials_.reset(); |
| 81 | |
| 82 | // At this point we don't know the email address so we need to fetch it. |
| 83 | email_discovery_ = true; |
| 84 | gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 85 | } |
| 86 | |
| 87 | void OAuthTokenGetterImpl::OnRefreshTokenResponse( |
| 88 | const std::string& access_token, |
| 89 | int expires_seconds) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 90 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 91 | DCHECK(authorization_credentials_); |
nicholss | f620a47c | 2017-02-02 23:39:08 | [diff] [blame] | 92 | VLOG(1) << "Received OAuth token."; |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 93 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 94 | // Update the access token and any other auto-update timers. |
| 95 | UpdateAccessToken(access_token, expires_seconds); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 96 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 97 | if (!authorization_credentials_->is_service_account && !email_verified_) { |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 98 | gaia_oauth_client_->GetUserEmail(access_token, kMaxRetries, this); |
| 99 | } else { |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 100 | response_pending_ = false; |
| 101 | NotifyTokenCallbacks(OAuthTokenGetterImpl::SUCCESS, |
| 102 | authorization_credentials_->login, |
| 103 | oauth_access_token_); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 104 | } |
| 105 | } |
| 106 | |
| 107 | void OAuthTokenGetterImpl::OnGetUserEmailResponse( |
| 108 | const std::string& user_email) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 109 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 110 | DCHECK(authorization_credentials_); |
nicholss | f620a47c | 2017-02-02 23:39:08 | [diff] [blame] | 111 | VLOG(1) << "Received user info."; |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 112 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 113 | if (email_discovery_) { |
| 114 | authorization_credentials_->login = user_email; |
| 115 | email_discovery_ = false; |
| 116 | NotifyUpdatedCallbacks(authorization_credentials_->login, |
| 117 | authorization_credentials_->refresh_token); |
| 118 | } else if (user_email != authorization_credentials_->login) { |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 119 | LOG(ERROR) << "OAuth token and email address do not refer to " |
| 120 | "the same account."; |
| 121 | OnOAuthError(); |
| 122 | return; |
| 123 | } |
| 124 | |
| 125 | email_verified_ = true; |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 126 | response_pending_ = false; |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 127 | |
| 128 | // Now that we've refreshed the token and verified that it's for the correct |
| 129 | // user account, try to connect using the new token. |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 130 | NotifyTokenCallbacks(OAuthTokenGetterImpl::SUCCESS, user_email, |
| 131 | oauth_access_token_); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 132 | } |
| 133 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 134 | void OAuthTokenGetterImpl::UpdateAccessToken(const std::string& access_token, |
| 135 | int expires_seconds) { |
| 136 | oauth_access_token_ = access_token; |
| 137 | base::TimeDelta token_expiration = |
| 138 | base::TimeDelta::FromSeconds(expires_seconds) - |
| 139 | base::TimeDelta::FromSeconds(kTokenUpdateTimeBeforeExpirySeconds); |
| 140 | access_token_expiry_time_ = base::Time::Now() + token_expiration; |
| 141 | |
| 142 | if (refresh_timer_) { |
| 143 | refresh_timer_->Stop(); |
| 144 | refresh_timer_->Start(FROM_HERE, token_expiration, this, |
| 145 | &OAuthTokenGetterImpl::RefreshAccessToken); |
| 146 | } |
| 147 | } |
| 148 | |
| 149 | void OAuthTokenGetterImpl::NotifyTokenCallbacks( |
| 150 | Status status, |
| 151 | const std::string& user_email, |
| 152 | const std::string& access_token) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 153 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 154 | std::queue<TokenCallback> callbacks(pending_callbacks_); |
| 155 | pending_callbacks_ = std::queue<TokenCallback>(); |
| 156 | |
| 157 | while (!callbacks.empty()) { |
| 158 | callbacks.front().Run(status, user_email, access_token); |
| 159 | callbacks.pop(); |
| 160 | } |
| 161 | } |
| 162 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 163 | void OAuthTokenGetterImpl::NotifyUpdatedCallbacks( |
| 164 | const std::string& user_email, |
| 165 | const std::string& refresh_token) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 166 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 167 | if (credentials_updated_callback_) { |
| 168 | credentials_updated_callback_.Run(user_email, refresh_token); |
| 169 | } |
| 170 | } |
| 171 | |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 172 | void OAuthTokenGetterImpl::OnOAuthError() { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 173 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 174 | LOG(ERROR) << "OAuth: invalid credentials."; |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 175 | response_pending_ = false; |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 176 | |
| 177 | // Throw away invalid credentials and force a refresh. |
| 178 | oauth_access_token_.clear(); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 179 | access_token_expiry_time_ = base::Time(); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 180 | email_verified_ = false; |
| 181 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 182 | NotifyTokenCallbacks(OAuthTokenGetterImpl::AUTH_ERROR, std::string(), |
| 183 | std::string()); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 184 | } |
| 185 | |
| 186 | void OAuthTokenGetterImpl::OnNetworkError(int response_code) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 187 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 188 | LOG(ERROR) << "Network error when trying to update OAuth token: " |
| 189 | << response_code; |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 190 | response_pending_ = false; |
| 191 | NotifyTokenCallbacks(OAuthTokenGetterImpl::NETWORK_ERROR, std::string(), |
| 192 | std::string()); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 193 | } |
| 194 | |
| 195 | void OAuthTokenGetterImpl::CallWithToken(const TokenCallback& on_access_token) { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 196 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 197 | if (intermediate_credentials_) { |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 198 | pending_callbacks_.push(on_access_token); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 199 | if (!response_pending_) { |
| 200 | GetOauthTokensFromAuthCode(); |
| 201 | } |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 202 | } else { |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 203 | bool need_new_auth_token = |
| 204 | access_token_expiry_time_.is_null() || |
| 205 | base::Time::Now() >= access_token_expiry_time_ || |
| 206 | (!authorization_credentials_->is_service_account && !email_verified_); |
| 207 | |
| 208 | if (need_new_auth_token) { |
| 209 | pending_callbacks_.push(on_access_token); |
| 210 | if (!response_pending_) { |
| 211 | RefreshAccessToken(); |
| 212 | } |
| 213 | } else { |
| 214 | on_access_token.Run(SUCCESS, authorization_credentials_->login, |
| 215 | oauth_access_token_); |
| 216 | } |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 217 | } |
| 218 | } |
| 219 | |
sergeyu | 6134f58 | 2015-12-12 01:09:06 | [diff] [blame] | 220 | void OAuthTokenGetterImpl::InvalidateCache() { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 221 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 222 | access_token_expiry_time_ = base::Time(); |
sergeyu | 6134f58 | 2015-12-12 01:09:06 | [diff] [blame] | 223 | } |
| 224 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 225 | void OAuthTokenGetterImpl::GetOauthTokensFromAuthCode() { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 226 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 227 | VLOG(1) << "Fetching OAuth token from Auth Code."; |
| 228 | DCHECK(!response_pending_); |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 229 | |
| 230 | // Service accounts use different API keys, as they use the client app flow. |
| 231 | google_apis::OAuth2Client oauth2_client = |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 232 | intermediate_credentials_->is_service_account |
| 233 | ? google_apis::CLIENT_REMOTING_HOST |
| 234 | : google_apis::CLIENT_REMOTING; |
| 235 | |
| 236 | std::string redirect_uri; |
| 237 | if (intermediate_credentials_->oauth_redirect_uri.empty()) { |
| 238 | if (intermediate_credentials_->is_service_account) { |
| 239 | redirect_uri = "oob"; |
| 240 | } else { |
| 241 | redirect_uri = GetDefaultOauthRedirectUrl(); |
| 242 | } |
| 243 | } else { |
| 244 | redirect_uri = intermediate_credentials_->oauth_redirect_uri; |
| 245 | } |
| 246 | |
| 247 | gaia::OAuthClientInfo client_info = { |
| 248 | google_apis::GetOAuth2ClientID(oauth2_client), |
| 249 | google_apis::GetOAuth2ClientSecret(oauth2_client), redirect_uri}; |
| 250 | |
| 251 | response_pending_ = true; |
| 252 | |
| 253 | gaia_oauth_client_->GetTokensFromAuthCode( |
| 254 | client_info, intermediate_credentials_->authorization_code, kMaxRetries, |
| 255 | this); |
| 256 | } |
| 257 | |
| 258 | void OAuthTokenGetterImpl::RefreshAccessToken() { |
gab | bf77513a | 2017-06-01 14:35:34 | [diff] [blame] | 259 | DCHECK_CALLED_ON_VALID_SEQUENCE(sequence_checker_); |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 260 | VLOG(1) << "Refreshing OAuth Access token."; |
| 261 | DCHECK(!response_pending_); |
| 262 | |
| 263 | // Service accounts use different API keys, as they use the client app flow. |
| 264 | google_apis::OAuth2Client oauth2_client = |
| 265 | authorization_credentials_->is_service_account |
| 266 | ? google_apis::CLIENT_REMOTING_HOST |
| 267 | : google_apis::CLIENT_REMOTING; |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 268 | |
| 269 | gaia::OAuthClientInfo client_info = { |
| 270 | google_apis::GetOAuth2ClientID(oauth2_client), |
| 271 | google_apis::GetOAuth2ClientSecret(oauth2_client), |
| 272 | // Redirect URL is only used when getting tokens from auth code. It |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 273 | // is not required when getting access tokens from refresh tokens. |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 274 | ""}; |
| 275 | |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 276 | response_pending_ = true; |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 277 | std::vector<std::string> empty_scope_list; // Use scope from refresh token. |
| 278 | gaia_oauth_client_->RefreshToken(client_info, |
nicholss | 1e1a3f89 | 2017-02-21 17:55:05 | [diff] [blame] | 279 | authorization_credentials_->refresh_token, |
jrw | dd87d07c | 2015-06-02 02:44:34 | [diff] [blame] | 280 | empty_scope_list, kMaxRetries, this); |
| 281 | } |
| 282 | |
| 283 | } // namespace remoting |