Chromium Code Reviews| Index: chrome/browser/managed_mode/managed_user_token_fetcher.cc |
| diff --git a/chrome/browser/managed_mode/managed_user_token_fetcher.cc b/chrome/browser/managed_mode/managed_user_token_fetcher.cc |
| new file mode 100644 |
| index 0000000000000000000000000000000000000000..59a33bc9573f42f457e3fa15b1d0a6580f8600a9 |
| --- /dev/null |
| +++ b/chrome/browser/managed_mode/managed_user_token_fetcher.cc |
| @@ -0,0 +1,277 @@ |
| +// Copyright 2013 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 "chrome/browser/managed_mode/managed_user_token_fetcher.h" |
| + |
| +#include "base/callback.h" |
| +#include "base/json/json_reader.h" |
| +#include "base/logging.h" |
| +#include "base/string16.h" |
| +#include "base/stringprintf.h" |
| +#include "base/utf_string_conversions.h" |
| +#include "base/values.h" |
| +#include "chrome/browser/signin/oauth2_token_service.h" |
| +#include "google_apis/gaia/gaia_oauth_client.h" |
| +#include "google_apis/gaia/gaia_urls.h" |
| +#include "google_apis/gaia/google_service_auth_error.h" |
| +#include "google_apis/gaia/oauth2_api_call_flow.h" |
| +#include "net/base/escape.h" |
| +#include "net/base/load_flags.h" |
| +#include "net/base/net_errors.h" |
| +#include "net/http/http_status_code.h" |
| +#include "net/url_request/url_fetcher.h" |
| +#include "net/url_request/url_request_status.h" |
| + |
| +using base::Time; |
| +using gaia::GaiaOAuthClient; |
| +using net::URLFetcher; |
| +using net::URLFetcherDelegate; |
| +using net::URLRequestContextGetter; |
| + |
| +namespace { |
| + |
| +const int kNumRetries = 1; |
| + |
| +static const char kChromeSyncManagedScope[] = |
| + "https://www.googleapis.com/auth/chromesync_playpen"; |
| + |
| +static const char kIssueTokenBodyFormat[] = |
| + "client_id=%s" |
| + "&scope=&%s" |
| + "&response_type=code" |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
I wonder if it makes sense to extend OAuth2MintTok
Bernhard Bauer
2013/05/29 11:41:45
Well, there are also other problems:
* OAuth2MintT
|
| + "&profile_id=%s" |
| + "&profile_name=%s" |
| + "&device_name=%s"; |
| + |
| +static const char kAuthorizationHeaderFormat[] = |
| + "Authorization: Bearer %s"; |
| + |
| +static const char kCodeKey[] = "code"; |
| + |
| +class ManagedUserTokenFetcherImpl : public ManagedUserTokenFetcher, |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
BTW, I like this pattern of keeping the Impl class
|
| + public OAuth2TokenService::Consumer, |
| + public URLFetcherDelegate, |
| + public GaiaOAuthClient::Delegate { |
| + public: |
| + ManagedUserTokenFetcherImpl(OAuth2TokenService* oauth2_token_service, |
| + URLRequestContextGetter* context); |
| + virtual ~ManagedUserTokenFetcherImpl(); |
| + |
| + // ManagedUserTokenFetcher implementation: |
| + virtual void Start(const std::string& managed_user_id, |
| + const string16& name, |
| + const std::string& device_name, |
| + const TokenCallback& callback) OVERRIDE; |
| + |
| + protected: |
| + // OAuth2TokenService::Consumer implementation: |
| + virtual void OnGetTokenSuccess(const OAuth2TokenService::Request* request, |
| + const std::string& access_token, |
| + const Time& expiration_time) OVERRIDE; |
| + virtual void OnGetTokenFailure(const OAuth2TokenService::Request* request, |
| + const GoogleServiceAuthError& error) OVERRIDE; |
| + |
| + // net::URLFetcherDelegate implementation. |
| + virtual void OnURLFetchComplete(const URLFetcher* source) OVERRIDE; |
| + |
| + // GaiaOAuthClient::Delegate implementation: |
| + virtual void OnGetTokensResponse(const std::string& refresh_token, |
| + const std::string& access_token, |
| + int expires_in_seconds) OVERRIDE; |
| + virtual void OnRefreshTokenResponse(const std::string& access_token, |
| + int expires_in_seconds) OVERRIDE; |
| + virtual void OnOAuthError() OVERRIDE; |
| + virtual void OnNetworkError(int response_code) OVERRIDE; |
| + |
| + private: |
| + // Requests an access token, which is the first thing we need. This is where |
| + // we restart when the returned access token has expired. |
| + void StartFetching(); |
| + |
| + void DispatchNetworkError(int error_code); |
| + void DispatchGoogleServiceAuthError(GoogleServiceAuthError::State foo); |
| + |
| + OAuth2TokenService* oauth2_token_service_; |
| + URLRequestContextGetter* context_; |
| + |
| + std::string device_name_; |
| + std::string managed_user_id_; |
| + string16 name_; |
| + TokenCallback callback_; |
| + |
| + scoped_ptr<OAuth2TokenService::Request> access_token_request_; |
| + std::string access_token_; |
| + scoped_ptr<URLFetcher> url_fetcher_; |
| + scoped_ptr<GaiaOAuthClient> gaia_oauth_client_; |
| +}; |
| + |
| +ManagedUserTokenFetcherImpl::ManagedUserTokenFetcherImpl( |
| + OAuth2TokenService* oauth2_token_service, |
| + URLRequestContextGetter* context) |
| + : oauth2_token_service_(oauth2_token_service), |
| + context_(context) {} |
| + |
| +ManagedUserTokenFetcherImpl::~ManagedUserTokenFetcherImpl() {} |
| + |
| +void ManagedUserTokenFetcherImpl::Start( |
| + const std::string& managed_user_id, |
| + const string16& name, |
| + const std::string& device_name, |
| + const TokenCallback& callback) { |
| + DCHECK(callback_.is_null()); |
| + managed_user_id_ = managed_user_id; |
| + name_ = name; |
| + device_name_ = device_name; |
| + callback_ = callback; |
| + StartFetching(); |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
So, let's imagine that we have a really slow inter
Bernhard Bauer
2013/05/29 11:41:45
Yes, at least on desktop. I'll ask about Chrome OS
Andrew T Wilson (Slow)
2013/05/29 12:05:33
I guess I don't understand the flow, then.
1) Use
Bernhard Bauer
2013/05/29 12:56:55
Yes. I'm planning on adding that as soon as Pam ha
Andrew T Wilson (Slow)
2013/05/29 13:12:17
Adding a TODO in the header file is sufficient for
Bernhard Bauer
2013/05/29 14:11:15
Done.
|
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::StartFetching() { |
| + // An empty set of scopes means the same scope as the refresh token. |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
We should document this in OAuth2TokenService (tha
Bernhard Bauer
2013/05/29 11:41:45
Ok. Alternatively, I could pass in the login scope
Andrew T Wilson (Slow)
2013/05/29 12:05:33
Passing in an explicit scope would be ideal.
Bernhard Bauer
2013/05/29 12:56:55
Done.
|
| + OAuth2TokenService::ScopeSet scopes; |
| + access_token_request_ = oauth2_token_service_->StartRequest(scopes, this); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnGetTokenSuccess( |
| + const OAuth2TokenService::Request* request, |
| + const std::string& access_token, |
| + const Time& expiration_time) { |
| + DCHECK_EQ(access_token_request_.get(), request); |
| + access_token_ = access_token; |
| + |
| + GURL url(GaiaUrls::GetInstance()->oauth2_issue_token_url()); |
| + // GaiaOAuthClient uses id 0, so we use 1 to distinguish the requests in |
| + // unit tests. |
| + int id = 1; |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
nit: const int id
Bernhard Bauer
2013/05/29 11:41:45
Done.
|
| + |
| + url_fetcher_.reset(URLFetcher::Create(id, url, URLFetcher::POST, this)); |
| + |
| + url_fetcher_->SetRequestContext(context_); |
| + url_fetcher_->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES | |
| + net::LOAD_DO_NOT_SAVE_COOKIES); |
| + url_fetcher_->SetAutomaticallyRetryOnNetworkChanges(kNumRetries); |
| + url_fetcher_->AddExtraRequestHeader( |
| + base::StringPrintf(kAuthorizationHeaderFormat, access_token.c_str())); |
| + |
| + std::string body = base::StringPrintf( |
| + kIssueTokenBodyFormat, |
| + net::EscapeUrlEncodedData( |
| + GaiaUrls::GetInstance()->oauth2_chrome_client_id(), true).c_str(), |
| + net::EscapeUrlEncodedData(kChromeSyncManagedScope, true).c_str(), |
| + net::EscapeUrlEncodedData(managed_user_id_, true).c_str(), |
| + net::EscapeUrlEncodedData(UTF16ToUTF8(name_), true).c_str(), |
| + net::EscapeUrlEncodedData(device_name_, true).c_str()); |
| + url_fetcher_->SetUploadData("application/x-www-form-urlencoded", body); |
| + |
| + url_fetcher_->Start(); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnGetTokenFailure( |
| + const OAuth2TokenService::Request* request, |
| + const GoogleServiceAuthError& error) { |
| + DCHECK_EQ(access_token_request_.get(), request); |
| + callback_.Run(error, std::string()); |
| + callback_.Reset(); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnURLFetchComplete( |
| + const URLFetcher* source) { |
| + const net::URLRequestStatus& status = source->GetStatus(); |
| + if (!status.is_success()) { |
| + DispatchNetworkError(status.error()); |
| + return; |
| + } |
| + |
| + int response_code = source->GetResponseCode(); |
| + if (response_code == net::HTTP_UNAUTHORIZED) { |
| + oauth2_token_service_->InvalidateToken(OAuth2TokenService::ScopeSet(), |
| + access_token_); |
| + StartFetching(); |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
If there's some bug that causes the server to perm
Bernhard Bauer
2013/05/29 11:41:45
Good idea! Done.
|
| + return; |
| + } |
| + |
| + if (response_code != net::HTTP_OK) { |
| + // TODO(bauerb): We should return the HTTP response code somehow. |
| + LOG(WARNING) << "HTTP error " << response_code; |
| + DispatchGoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED); |
| + return; |
| + } |
| + |
| + std::string response_body; |
| + source->GetResponseAsString(&response_body); |
| + scoped_ptr<base::Value> value(base::JSONReader::Read(response_body)); |
| + DictionaryValue* dict = NULL; |
| + if (!value.get() || !value->GetAsDictionary(&dict)) { |
| + DispatchNetworkError(net::ERR_INVALID_RESPONSE); |
| + return; |
| + } |
| + std::string auth_code; |
| + if (!dict->GetString(kCodeKey, &auth_code)) { |
| + DispatchNetworkError(net::ERR_INVALID_RESPONSE); |
| + return; |
| + } |
| + |
| + gaia::OAuthClientInfo client_info; |
| + GaiaUrls* urls = GaiaUrls::GetInstance(); |
| + client_info.client_id = urls->oauth2_chrome_client_id(); |
| + client_info.client_secret = urls->oauth2_chrome_client_secret(); |
| + gaia_oauth_client_.reset( |
| + new gaia::GaiaOAuthClient(GaiaUrls::GetInstance()->oauth2_token_url(), |
| + context_)); |
| + gaia_oauth_client_->GetTokensFromAuthCode(client_info, auth_code, kNumRetries, |
| + this); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnGetTokensResponse( |
| + const std::string& refresh_token, |
| + const std::string& access_token, |
| + int expires_in_seconds) { |
| + // TODO(bauerb): It would be nice if we could pass the access token as well, |
| + // so we don't need to fetch another one immediately. |
| + GoogleServiceAuthError error(GoogleServiceAuthError::NONE); |
|
Andrew T Wilson (Slow)
2013/05/29 12:05:33
Call DispatchGoogleServiceAuthError(GoogleServiceA
Bernhard Bauer
2013/05/29 12:56:55
Done.
|
| + callback_.Run(error, refresh_token); |
| + callback_.Reset(); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnRefreshTokenResponse( |
| + const std::string& access_token, |
| + int expires_in_seconds) { |
| + NOTREACHED(); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnOAuthError() { |
| + DispatchGoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::OnNetworkError(int response_code) { |
| + // TODO(bauerb): We should return the HTTP response code somehow. |
| + LOG(WARNING) << "HTTP error " << response_code; |
|
Andrew T Wilson (Slow)
2013/05/29 12:05:33
I'm always leery of adding LOG() statements since
Bernhard Bauer
2013/05/29 12:56:55
Done. It does mean that you can only see the HTTP
|
| + DispatchGoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::DispatchNetworkError(int error_code) { |
| + callback_.Run(GoogleServiceAuthError::FromConnectionError(error_code), |
|
Andrew T Wilson (Slow)
2013/05/28 13:10:08
Maybe change this to:
{
DispatchGoogleServiceAu
Bernhard Bauer
2013/05/29 11:41:45
Do you mean adding an overload that takes a Google
Andrew T Wilson (Slow)
2013/05/29 12:05:33
I mean that instead of calling callback_.Run() and
Bernhard Bauer
2013/05/29 12:56:55
Done.
|
| + std::string()); |
| + callback_.Reset(); |
| +} |
| + |
| +void ManagedUserTokenFetcherImpl::DispatchGoogleServiceAuthError( |
| + GoogleServiceAuthError::State state) { |
| + GoogleServiceAuthError error(state); |
| + callback_.Run(error, std::string()); |
| + callback_.Reset(); |
| +} |
| + |
| +} // namespace |
| + |
| +// static |
| +scoped_ptr<ManagedUserTokenFetcher> ManagedUserTokenFetcher::Create( |
| + OAuth2TokenService* oauth2_token_service, |
| + URLRequestContextGetter* context) { |
| + scoped_ptr<ManagedUserTokenFetcher> fetcher( |
| + new ManagedUserTokenFetcherImpl(oauth2_token_service, context)); |
| + return fetcher.Pass(); |
| +} |
| + |
| +ManagedUserTokenFetcher::~ManagedUserTokenFetcher() {} |