Commit 271398aa authored by munjal@chromium.org's avatar munjal@chromium.org

Add code to mint OAuth tokens from login-scoped OAuth token.

- Add a fetcher class, OAuth2TokenMintFetcher, to abstract OAuth2 token mint process.
- Add tests.

The work to use this in AppNotifyChannlesetup will be done in upcoming patch.
Review URL: https://chromiumcodereview.appspot.com/9549013

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@124486 0039d316-1c4b-4281-b951-d872f2087c98
parent d3b7e7cc
......@@ -399,6 +399,9 @@
'common/net/gaia/oauth2_access_token_consumer.h',
'common/net/gaia/oauth2_access_token_fetcher.cc',
'common/net/gaia/oauth2_access_token_fetcher.h',
'common/net/gaia/oauth2_mint_token_consumer.h',
'common/net/gaia/oauth2_mint_token_fetcher.cc',
'common/net/gaia/oauth2_mint_token_fetcher.h',
'common/net/gaia/oauth2_revocation_consumer.h',
'common/net/gaia/oauth2_revocation_fetcher.cc',
'common/net/gaia/oauth2_revocation_fetcher.h',
......
......@@ -2086,6 +2086,7 @@
'common/net/gaia/google_service_auth_error_unittest.cc',
'common/net/gaia/oauth_request_signer_unittest.cc',
'common/net/gaia/oauth2_access_token_fetcher_unittest.cc',
'common/net/gaia/oauth2_mint_token_fetcher_unittest.cc',
'common/net/gaia/oauth2_revocation_fetcher_unittest.cc',
'common/service_process_util_unittest.cc',
'common/string_ordinal_unittest.cc',
......
......@@ -38,6 +38,8 @@ const char kClientLoginToOAuth2Url[] =
"https://accounts.google.com/o/oauth2/programmatic_auth";
const char kOAuth2TokenUrl[] =
"https://accounts.google.com/o/oauth2/token";
const char kOAuth2IssueTokenUrl[] =
"https://www.googleapis.com/oauth2/v2/IssueToken";
} // namespacce
GaiaUrls* GaiaUrls::GetInstance() {
......@@ -80,6 +82,7 @@ GaiaUrls::GaiaUrls() {
oauth2_chrome_client_secret_ = kOAuth2ChromeClientSecret;
client_login_to_oauth2_url_ = kClientLoginToOAuth2Url;
oauth2_token_url_ = kOAuth2TokenUrl;
oauth2_issue_token_url_ = kOAuth2IssueTokenUrl;
}
GaiaUrls::~GaiaUrls() {
......@@ -160,3 +163,7 @@ const std::string& GaiaUrls::client_login_to_oauth2_url() {
const std::string& GaiaUrls::oauth2_token_url() {
return oauth2_token_url_;
}
const std::string& GaiaUrls::oauth2_issue_token_url() {
return oauth2_issue_token_url_;
}
......@@ -38,6 +38,7 @@ class GaiaUrls {
const std::string& oauth2_chrome_client_secret();
const std::string& client_login_to_oauth2_url();
const std::string& oauth2_token_url();
const std::string& oauth2_issue_token_url();
private:
GaiaUrls();
......@@ -67,6 +68,7 @@ class GaiaUrls {
std::string oauth2_chrome_client_secret_;
std::string client_login_to_oauth2_url_;
std::string oauth2_token_url_;
std::string oauth2_issue_token_url_;
DISALLOW_COPY_AND_ASSIGN(GaiaUrls);
};
......
// 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.
#ifndef CHROME_COMMON_NET_GAIA_OAUTH2_MINT_TOKEN_CONSUMER_H_
#define CHROME_COMMON_NET_GAIA_OAUTH2_MINT_TOKEN_CONSUMER_H_
#pragma once
#include <string>
class GoogleServiceAuthError;
// An interface that defines the callbacks for consumers to which
// OAuth2MintTokenFetcher can return results.
class OAuth2MintTokenConsumer {
public:
virtual ~OAuth2MintTokenConsumer() {}
virtual void OnMintTokenSuccess(const std::string& access_token) {}
virtual void OnMintTokenFailure(const GoogleServiceAuthError& error) {}
};
#endif // CHROME_COMMON_NET_GAIA_OAUTH2_MINT_TOKEN_CONSUMER_H_
// 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 "chrome/common/net/gaia/oauth2_mint_token_fetcher.h"
#include <algorithm>
#include <string>
#include "base/json/json_reader.h"
#include "base/string_util.h"
#include "base/stringprintf.h"
#include "base/values.h"
#include "chrome/common/net/gaia/gaia_urls.h"
#include "chrome/common/net/gaia/google_service_auth_error.h"
#include "net/base/escape.h"
#include "net/base/load_flags.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request_context_getter.h"
#include "net/url_request/url_request_status.h"
using content::URLFetcher;
using content::URLFetcherDelegate;
using net::ResponseCookies;
using net::URLRequestContextGetter;
using net::URLRequestStatus;
namespace {
static const char kAuthorizationHeaderFormat[] =
"Authorization: Bearer %s";
static const char kOAuth2IssueTokenBodyFormat[] =
"force=true"
"&response_type=token"
"&scope=%s"
"&client_id=%s"
"&origin=%s";
static const char kAccessTokenKey[] = "token";
static GoogleServiceAuthError CreateAuthError(URLRequestStatus status) {
CHECK(!status.is_success());
if (status.status() == URLRequestStatus::CANCELED) {
return GoogleServiceAuthError(GoogleServiceAuthError::REQUEST_CANCELED);
} else {
DLOG(WARNING) << "Could not reach Google Accounts servers: errno "
<< status.error();
return GoogleServiceAuthError::FromConnectionError(status.error());
}
}
static URLFetcher* CreateFetcher(URLRequestContextGetter* getter,
const GURL& url,
const std::string& headers,
const std::string& body,
URLFetcherDelegate* delegate) {
bool empty_body = body.empty();
URLFetcher* result = URLFetcher::Create(
0, url,
empty_body ? URLFetcher::GET : URLFetcher::POST,
delegate);
result->SetRequestContext(getter);
result->SetLoadFlags(net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES);
if (!empty_body)
result->SetUploadData("application/x-www-form-urlencoded", body);
if (!headers.empty())
result->SetExtraRequestHeaders(headers);
return result;
}
} // namespace
OAuth2MintTokenFetcher::OAuth2MintTokenFetcher(
OAuth2MintTokenConsumer* consumer,
URLRequestContextGetter* getter,
const std::string& source)
: consumer_(consumer),
getter_(getter),
source_(source),
state_(INITIAL) { }
OAuth2MintTokenFetcher::~OAuth2MintTokenFetcher() { }
void OAuth2MintTokenFetcher::CancelRequest() {
fetcher_.reset();
}
void OAuth2MintTokenFetcher::Start(const std::string& oauth_login_access_token,
const std::string& client_id,
const std::vector<std::string>& scopes,
const std::string& origin) {
oauth_login_access_token_ = oauth_login_access_token;
client_id_ = client_id;
scopes_ = scopes;
origin_ = origin;
StartMintToken();
}
void OAuth2MintTokenFetcher::StartMintToken() {
CHECK_EQ(INITIAL, state_);
state_ = MINT_TOKEN_STARTED;
fetcher_.reset(CreateFetcher(
getter_,
MakeMintTokenUrl(),
MakeMintTokenHeader(oauth_login_access_token_),
MakeMintTokenBody(client_id_, scopes_, origin_),
this));
fetcher_->Start(); // OnURLFetchComplete will be called.
}
void OAuth2MintTokenFetcher::EndMintToken(const URLFetcher* source) {
CHECK_EQ(MINT_TOKEN_STARTED, state_);
state_ = MINT_TOKEN_DONE;
URLRequestStatus status = source->GetStatus();
if (!status.is_success()) {
OnMintTokenFailure(CreateAuthError(status));
return;
}
if (source->GetResponseCode() != net::HTTP_OK) {
OnMintTokenFailure(GoogleServiceAuthError(
GoogleServiceAuthError::INVALID_GAIA_CREDENTIALS));
return;
}
// The request was successfully fetched and it returned OK.
// Parse out the access token.
std::string access_token;
ParseMintTokenResponse(source, &access_token);
OnMintTokenSuccess(access_token);
}
void OAuth2MintTokenFetcher::OnMintTokenSuccess(
const std::string& access_token) {
consumer_->OnMintTokenSuccess(access_token);
}
void OAuth2MintTokenFetcher::OnMintTokenFailure(GoogleServiceAuthError error) {
state_ = ERROR_STATE;
consumer_->OnMintTokenFailure(error);
}
void OAuth2MintTokenFetcher::OnURLFetchComplete(const URLFetcher* source) {
CHECK(source);
CHECK_EQ(MINT_TOKEN_STARTED, state_);
EndMintToken(source);
}
// static
GURL OAuth2MintTokenFetcher::MakeMintTokenUrl() {
return GURL(GaiaUrls::GetInstance()->oauth2_issue_token_url());
}
// static
std::string OAuth2MintTokenFetcher::MakeMintTokenHeader(
const std::string& access_token) {
return StringPrintf(kAuthorizationHeaderFormat, access_token.c_str());
}
// static
std::string OAuth2MintTokenFetcher::MakeMintTokenBody(
const std::string& client_id,
const std::vector<std::string>& scopes,
const std::string& origin) {
return StringPrintf(
kOAuth2IssueTokenBodyFormat,
net::EscapeUrlEncodedData(JoinString(scopes, ','), true).c_str(),
net::EscapeUrlEncodedData(client_id, true).c_str(),
net::EscapeUrlEncodedData(origin, true).c_str());
}
// static
bool OAuth2MintTokenFetcher::ParseMintTokenResponse(
const URLFetcher* source,
std::string* access_token) {
CHECK(source);
CHECK(access_token);
std::string data;
source->GetResponseAsString(&data);
base::JSONReader reader;
scoped_ptr<base::Value> value(reader.Read(data, false));
if (!value.get() || value->GetType() != base::Value::TYPE_DICTIONARY)
return false;
DictionaryValue* dict = static_cast<DictionaryValue*>(value.get());
return dict->GetString(kAccessTokenKey, access_token);
}
// 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.
#ifndef CHROME_COMMON_NET_GAIA_OAUTH2_MINT_TOKEN_FETCHER_H_
#define CHROME_COMMON_NET_GAIA_OAUTH2_MINT_TOKEN_FETCHER_H_
#pragma once
#include <string>
#include <vector>
#include "base/gtest_prod_util.h"
#include "base/memory/scoped_ptr.h"
#include "chrome/common/net/gaia/oauth2_mint_token_consumer.h"
#include "content/public/common/url_fetcher.h"
#include "content/public/common/url_fetcher_delegate.h"
#include "googleurl/src/gurl.h"
class OAuth2MintTokenFetcherTest;
namespace net {
class URLRequestContextGetter;
class URLRequestStatus;
}
// Abstracts the details to mint new OAuth2 tokens from OAuth2 login scoped
// token.
//
// This class should be used on a single thread, but it can be whichever thread
// that you like.
// Also, do not reuse the same instance. Once Start() is called, the instance
// should not be reused.
//
// Usage:
// * Create an instance with a consumer.
// * Call Start()
// * The consumer passed in the constructor will be called on the same
// thread Start was called with the results.
//
// This class can handle one request at a time. To parallelize requests,
// create multiple instances.
class OAuth2MintTokenFetcher : public content::URLFetcherDelegate {
public:
OAuth2MintTokenFetcher(OAuth2MintTokenConsumer* consumer,
net::URLRequestContextGetter* getter,
const std::string& source);
virtual ~OAuth2MintTokenFetcher();
// Start the flow.
void Start(const std::string& oauth_login_access_token,
const std::string& client_id,
const std::vector<std::string>& scopes,
const std::string& origin);
void CancelRequest();
// Implementation of content::URLFetcherDelegate
virtual void OnURLFetchComplete(const content::URLFetcher* source) OVERRIDE;
private:
enum State {
INITIAL,
MINT_TOKEN_STARTED,
MINT_TOKEN_DONE,
ERROR_STATE,
};
// Helper methods for the flow.
void StartMintToken();
void EndMintToken(const content::URLFetcher* source);
// Helper mehtods for reporting back results.
void OnMintTokenSuccess(const std::string& access_token);
void OnMintTokenFailure(GoogleServiceAuthError error);
// Other helpers.
static GURL MakeMintTokenUrl();
static std::string MakeMintTokenHeader(const std::string& access_token);
static std::string MakeMintTokenBody(const std::string& client_id,
const std::vector<std::string>& scopes,
const std::string& origin);
static bool ParseMintTokenResponse(const content::URLFetcher* source,
std::string* access_token);
// State that is set during construction.
OAuth2MintTokenConsumer* const consumer_;
net::URLRequestContextGetter* const getter_;
std::string source_;
State state_;
// While a fetch is in progress.
scoped_ptr<content::URLFetcher> fetcher_;
std::string oauth_login_access_token_;
std::string client_id_;
std::vector<std::string> scopes_;
std::string origin_;
friend class OAuth2MintTokenFetcherTest;
FRIEND_TEST_ALL_PREFIXES(OAuth2MintTokenFetcherTest,
ParseMintTokenResponse);
DISALLOW_COPY_AND_ASSIGN(OAuth2MintTokenFetcher);
};
#endif // CHROME_COMMON_NET_GAIA_OAUTH2_MINT_TOKEN_FETCHER_H_
// 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.
//
// A complete set of unit tests for OAuth2MintTokenFetcher.
#include <string>
#include <vector>
#include "base/memory/scoped_ptr.h"
#include "base/message_loop.h"
#include "chrome/common/net/gaia/gaia_urls.h"
#include "chrome/common/net/gaia/google_service_auth_error.h"
#include "chrome/common/net/gaia/oauth2_mint_token_consumer.h"
#include "chrome/common/net/gaia/oauth2_mint_token_fetcher.h"
#include "chrome/test/base/testing_profile.h"
#include "content/public/common/url_fetcher.h"
#include "content/public/common/url_fetcher_delegate.h"
#include "content/public/common/url_fetcher_factory.h"
#include "content/test/test_browser_thread.h"
#include "content/test/test_url_fetcher_factory.h"
#include "googleurl/src/gurl.h"
#include "net/http/http_status_code.h"
#include "net/url_request/url_request.h"
#include "net/url_request/url_request_status.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
using content::BrowserThread;
using content::URLFetcher;
using content::URLFetcherDelegate;
using content::URLFetcherFactory;
using net::ResponseCookies;
using net::URLRequestStatus;
using testing::_;
using testing::Return;
namespace {
static const char kValidTokenResponse[] =
"{"
" \"token\": \"at1\","
" \"issueAdvice\": \"Auto\""
"}";
static const char kTokenResponseNoAccessToken[] =
"{"
" \"issueAdvice\": \"Auto\""
"}";
}
class MockUrlFetcherFactory : public ScopedURLFetcherFactory,
public URLFetcherFactory {
public:
MockUrlFetcherFactory()
: ScopedURLFetcherFactory(ALLOW_THIS_IN_INITIALIZER_LIST(this)) {
}
virtual ~MockUrlFetcherFactory() {}
MOCK_METHOD4(
CreateURLFetcher,
URLFetcher* (int id,
const GURL& url,
URLFetcher::RequestType request_type,
URLFetcherDelegate* d));
};
class MockOAuth2MintTokenConsumer : public OAuth2MintTokenConsumer {
public:
MockOAuth2MintTokenConsumer() {}
~MockOAuth2MintTokenConsumer() {}
MOCK_METHOD1(OnMintTokenSuccess, void(const std::string& access_token));
MOCK_METHOD1(OnMintTokenFailure,
void(const GoogleServiceAuthError& error));
};
class OAuth2MintTokenFetcherTest : public testing::Test {
public:
OAuth2MintTokenFetcherTest()
: ui_thread_(BrowserThread::UI, &message_loop_),
fetcher_(&consumer_, profile_.GetRequestContext(), "test") {
test_scopes_.push_back("scope1");
test_scopes_.push_back("scope1");
}
virtual ~OAuth2MintTokenFetcherTest() { }
virtual TestURLFetcher* SetupIssueToken(
bool fetch_succeeds, int response_code, const std::string& body) {
GURL url(GaiaUrls::GetInstance()->oauth2_issue_token_url());
TestURLFetcher* url_fetcher = new TestURLFetcher(0, url, &fetcher_);
URLRequestStatus::Status status =
fetch_succeeds ? URLRequestStatus::SUCCESS : URLRequestStatus::FAILED;
url_fetcher->set_status(URLRequestStatus(status, 0));
if (response_code != 0)
url_fetcher->set_response_code(response_code);
if (!body.empty())
url_fetcher->SetResponseString(body);
EXPECT_CALL(factory_, CreateURLFetcher(_, url, _, _))
.WillOnce(Return(url_fetcher));
return url_fetcher;
}
protected:
MessageLoop message_loop_;
content::TestBrowserThread ui_thread_;
MockUrlFetcherFactory factory_;
MockOAuth2MintTokenConsumer consumer_;
TestingProfile profile_;
OAuth2MintTokenFetcher fetcher_;
std::vector<std::string> test_scopes_;
};
TEST_F(OAuth2MintTokenFetcherTest, GetAccessTokenRequestFailure) {
TestURLFetcher* url_fetcher = SetupIssueToken(false, 0, "");
EXPECT_CALL(consumer_, OnMintTokenFailure(_)).Times(1);
fetcher_.Start("access_token1", "client1", test_scopes_, "extension1");
fetcher_.OnURLFetchComplete(url_fetcher);
}
TEST_F(OAuth2MintTokenFetcherTest, GetAccessTokenResponseCodeFailure) {
TestURLFetcher* url_fetcher = SetupIssueToken(
false, net::HTTP_FORBIDDEN, "");
EXPECT_CALL(consumer_, OnMintTokenFailure(_)).Times(1);
fetcher_.Start("access_token1", "client1", test_scopes_, "extension1");
fetcher_.OnURLFetchComplete(url_fetcher);
}
TEST_F(OAuth2MintTokenFetcherTest, Success) {
TestURLFetcher* url_fetcher = SetupIssueToken(
true, net::HTTP_OK, kValidTokenResponse);
EXPECT_CALL(consumer_, OnMintTokenSuccess("at1")).Times(1);
fetcher_.Start("access_token1", "client1", test_scopes_, "extension1");
fetcher_.OnURLFetchComplete(url_fetcher);
}
TEST_F(OAuth2MintTokenFetcherTest, ParseMintTokenResponse) {
{ // No body.
TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
std::string at;
EXPECT_FALSE(OAuth2MintTokenFetcher::ParseMintTokenResponse(
&url_fetcher, &at));
EXPECT_TRUE(at.empty());
}
{ // Bad json.
TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
url_fetcher.SetResponseString("foo");
std::string at;
EXPECT_FALSE(OAuth2MintTokenFetcher::ParseMintTokenResponse(
&url_fetcher, &at));
EXPECT_TRUE(at.empty());
}
{ // Valid json: access token missing.
TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
url_fetcher.SetResponseString(kTokenResponseNoAccessToken);
std::string at;
EXPECT_FALSE(OAuth2MintTokenFetcher::ParseMintTokenResponse(
&url_fetcher, &at));
EXPECT_TRUE(at.empty());
}
{ // Valid json: all good.
TestURLFetcher url_fetcher(0, GURL("www.google.com"), NULL);
url_fetcher.SetResponseString(kValidTokenResponse);
std::string at;
EXPECT_TRUE(OAuth2MintTokenFetcher::ParseMintTokenResponse(
&url_fetcher, &at));
EXPECT_EQ("at1", at);
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment