summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorcourage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-17 01:12:54 +0000
committercourage@chromium.org <courage@chromium.org@0039d316-1c4b-4281-b951-d872f2087c98>2013-05-17 01:12:54 +0000
commit1249a6e392f8f3b031ee70342ac98ec818edf7fc (patch)
treeb377b9beb9e858b31fc2313f0370e0ebe89a27af
parentace9afa7a2b3b40e593dfb4744b94cb6401d8308 (diff)
downloadchromium_src-1249a6e392f8f3b031ee70342ac98ec818edf7fc.zip
chromium_src-1249a6e392f8f3b031ee70342ac98ec818edf7fc.tar.gz
chromium_src-1249a6e392f8f3b031ee70342ac98ec818edf7fc.tar.bz2
Identity API: web-based scope approval dialogs for getAuthToken
When getAuthToken needs to prompt a user for permissions, it now uses a web-based authorization flow instead of native dialogs. BUG=228908 (step #4 of the bug description) Review URL: https://chromiumcodereview.appspot.com/15148007 git-svn-id: svn://svn.chromium.org/chrome/trunk/src@200683 0039d316-1c4b-4281-b951-d872f2087c98
-rw-r--r--chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc160
-rw-r--r--chrome/browser/extensions/api/identity/gaia_web_auth_flow.h97
-rw-r--r--chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc235
-rw-r--r--chrome/browser/extensions/api/identity/identity_api.cc77
-rw-r--r--chrome/browser/extensions/api/identity/identity_api.h25
-rw-r--r--chrome/browser/extensions/api/identity/identity_apitest.cc242
-rw-r--r--chrome/browser/extensions/api/identity/web_auth_flow.cc15
-rw-r--r--chrome/browser/extensions/api/identity/web_auth_flow.h2
-rw-r--r--chrome/chrome_browser_extensions.gypi2
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--google_apis/gaia/gaia_urls.cc6
-rw-r--r--google_apis/gaia/gaia_urls.h2
12 files changed, 736 insertions, 128 deletions
diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc
new file mode 100644
index 0000000..9362b3b
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.cc
@@ -0,0 +1,160 @@
+// Copyright (c) 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/extensions/api/identity/gaia_web_auth_flow.h"
+
+#include "base/string_util.h"
+#include "base/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
+#include "base/strings/string_split.h"
+#include "google_apis/gaia/gaia_urls.h"
+#include "net/base/escape.h"
+
+namespace extensions {
+
+GaiaWebAuthFlow::GaiaWebAuthFlow(Delegate* delegate,
+ Profile* profile,
+ chrome::HostDesktopType host_desktop_type,
+ const std::string& extension_id,
+ const OAuth2Info& oauth2_info)
+ : delegate_(delegate),
+ profile_(profile),
+ host_desktop_type_(host_desktop_type) {
+ const char kOAuth2RedirectPathFormat[] = "/%s#";
+ const char kOAuth2AuthorizeFormat[] =
+ "%s?response_type=token&approval_prompt=force&authuser=0&"
+ "client_id=%s&"
+ "scope=%s&"
+ "origin=chrome-extension://%s/&"
+ "redirect_uri=%s:/%s";
+
+ std::vector<std::string> client_id_parts;
+ base::SplitString(oauth2_info.client_id, '.', &client_id_parts);
+ std::reverse(client_id_parts.begin(), client_id_parts.end());
+ redirect_scheme_ = JoinString(client_id_parts, '.');
+
+ redirect_path_prefix_ =
+ base::StringPrintf(kOAuth2RedirectPathFormat, extension_id.c_str());
+
+ auth_url_ = GURL(base::StringPrintf(
+ kOAuth2AuthorizeFormat,
+ GaiaUrls::GetInstance()->oauth2_auth_url().c_str(),
+ oauth2_info.client_id.c_str(),
+ net::EscapeUrlEncodedData(JoinString(oauth2_info.scopes, ' '), true)
+ .c_str(),
+ extension_id.c_str(),
+ redirect_scheme_.c_str(),
+ extension_id.c_str()));
+}
+
+GaiaWebAuthFlow::~GaiaWebAuthFlow() {}
+
+void GaiaWebAuthFlow::Start() {
+ ubertoken_fetcher_.reset(new UbertokenFetcher(profile_, this));
+ ubertoken_fetcher_->StartFetchingToken();
+}
+
+void GaiaWebAuthFlow::OnUbertokenSuccess(const std::string& token) {
+ const char kMergeSessionQueryFormat[] = "?uberauth=%s&"
+ "continue=%s&"
+ "source=appsv2";
+
+ std::string merge_query = base::StringPrintf(
+ kMergeSessionQueryFormat,
+ net::EscapeUrlEncodedData(token, true).c_str(),
+ net::EscapeUrlEncodedData(auth_url_.spec(), true).c_str());
+ GURL merge_url(GaiaUrls::GetInstance()->merge_session_url() + merge_query);
+
+ web_flow_ = CreateWebAuthFlow(merge_url);
+ web_flow_->Start();
+}
+
+void GaiaWebAuthFlow::OnUbertokenFailure(const GoogleServiceAuthError& error) {
+ delegate_->OnGaiaFlowFailure(
+ GaiaWebAuthFlow::SERVICE_AUTH_ERROR, error, std::string());
+}
+
+void GaiaWebAuthFlow::OnAuthFlowFailure(WebAuthFlow::Failure failure) {
+ DCHECK(failure == WebAuthFlow::WINDOW_CLOSED);
+ delegate_->OnGaiaFlowFailure(
+ GaiaWebAuthFlow::WINDOW_CLOSED,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ std::string());
+}
+
+void GaiaWebAuthFlow::OnAuthFlowURLChange(const GURL& url) {
+ const char kOAuth2RedirectAccessTokenKey[] = "access_token";
+ const char kOAuth2RedirectErrorKey[] = "error";
+ const char kOAuth2ExpiresInKey[] = "expires_in";
+
+ // The format of the target URL is:
+ // reversed.oauth.client.id:/extensionid#access_token=TOKEN
+ //
+ // Because there is no double slash, everything after the scheme is
+ // interpreted as a path, including the fragment.
+
+ if (url.scheme() == redirect_scheme_ && !url.has_host() && !url.has_port() &&
+ StartsWithASCII(url.path(), redirect_path_prefix_, true)) {
+ web_flow_.reset();
+
+ std::string fragment =
+ url.path().substr(redirect_path_prefix_.length(), std::string::npos);
+ std::vector<std::pair<std::string, std::string> > pairs;
+ base::SplitStringIntoKeyValuePairs(fragment, '=', '&', &pairs);
+ std::string access_token;
+ std::string error;
+ std::string expiration;
+
+ for (std::vector<std::pair<std::string, std::string> >::iterator
+ it = pairs.begin();
+ it != pairs.end();
+ ++it) {
+ if (it->first == kOAuth2RedirectAccessTokenKey)
+ access_token = it->second;
+ else if (it->first == kOAuth2RedirectErrorKey)
+ error = it->second;
+ else if (it->first == kOAuth2ExpiresInKey)
+ expiration = it->second;
+ }
+
+ if (access_token.empty() && error.empty()) {
+ delegate_->OnGaiaFlowFailure(
+ GaiaWebAuthFlow::INVALID_REDIRECT,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ std::string());
+ } else if (!error.empty()) {
+ delegate_->OnGaiaFlowFailure(
+ GaiaWebAuthFlow::OAUTH_ERROR,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ error);
+ } else {
+ delegate_->OnGaiaFlowCompleted(access_token, expiration);
+ }
+ }
+}
+
+void GaiaWebAuthFlow::OnAuthFlowTitleChange(const std::string& title) {
+ // On the final page the title will be "Loading <redirect-url>".
+ // Treat it as though we'd really been redirected to <redirect-url>.
+ const char kRedirectPrefix[] = "Loading ";
+ std::string prefix(kRedirectPrefix);
+
+ if (StartsWithASCII(title, prefix, true)) {
+ GURL url(title.substr(prefix.length(), std::string::npos));
+ if (url.is_valid())
+ OnAuthFlowURLChange(url);
+ }
+}
+
+scoped_ptr<WebAuthFlow> GaiaWebAuthFlow::CreateWebAuthFlow(GURL url) {
+ gfx::Rect initial_bounds;
+ return scoped_ptr<WebAuthFlow>(new WebAuthFlow(this,
+ profile_,
+ url,
+ WebAuthFlow::INTERACTIVE,
+ initial_bounds,
+ host_desktop_type_));
+}
+
+} // extensions
diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h
new file mode 100644
index 0000000..8f9dba7
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow.h
@@ -0,0 +1,97 @@
+// Copyright (c) 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.
+
+#ifndef CHROME_BROWSER_EXTENSIONS_API_IDENTITY_GAIA_WEB_AUTH_FLOW_H_
+#define CHROME_BROWSER_EXTENSIONS_API_IDENTITY_GAIA_WEB_AUTH_FLOW_H_
+
+#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
+#include "chrome/browser/signin/ubertoken_fetcher.h"
+#include "chrome/browser/ui/host_desktop.h"
+#include "chrome/common/extensions/api/identity/oauth2_manifest_handler.h"
+
+namespace extensions {
+
+// Implements a web-based OAuth2 scope approval dialog. This flow has
+// four parts:
+// 1. Fetch an ubertoken for the signed-in user.
+// 2. Use the ubertoken to get session cookies using MergeSession.
+// 3. Start the OAuth flow and wait for final redirect.
+// 4. Parse results from the fragment component of the final redirect URI.
+//
+// The OAuth flow is a special version of the OAuth2 out-of-band flow
+// where the final response page's title contains the
+// redirect_uri. The redirect URI has an unusual format to prevent its
+// use in other contexts. The scheme of the URI is a reversed version
+// of the OAuth client ID, and the path starts with the Chrome
+// extension ID. For example, an app with the OAuth client ID
+// "32610281651.apps.googleusercontent.com" and a Chrome app ID
+// "kbinjhdkhikmpjoejcfofghmjjpidcnj", would get redirected to:
+//
+// com.googleusercontent.apps.32610281651:/kbinjhdkhikmpjoejcfofghmjjpidcnj
+//
+// Arriving at this URI completes the flow. The last response from
+// gaia does a JavaScript redirect to the special URI, but also
+// includes the same URI in its title. The navigation to this URI gets
+// filtered out because of its unusual protocol scheme, so
+// GaiaWebAuthFlow pulls it out of the window title instead.
+
+class GaiaWebAuthFlow : public UbertokenConsumer, public WebAuthFlow::Delegate {
+ public:
+ enum Failure {
+ WINDOW_CLOSED, // Window closed by user.
+ INVALID_REDIRECT, // Redirect parse error.
+ SERVICE_AUTH_ERROR, // Non-OAuth related authentication error
+ OAUTH_ERROR // Flow reached final redirect, which contained an error.
+ };
+
+ class Delegate {
+ public:
+ // Called when the flow fails prior to the final OAuth redirect,
+ virtual void OnGaiaFlowFailure(Failure failure,
+ GoogleServiceAuthError service_error,
+ const std::string& oauth_error) = 0;
+ // Called when the OAuth2 flow completes.
+ virtual void OnGaiaFlowCompleted(const std::string& access_token,
+ const std::string& expiration) = 0;
+ };
+
+ GaiaWebAuthFlow(Delegate* delegate,
+ Profile* profile,
+ chrome::HostDesktopType host_desktop_type,
+ const std::string& extension_id,
+ const OAuth2Info& oauth2_info);
+ virtual ~GaiaWebAuthFlow();
+
+ // Starts the flow by fetching an ubertoken. Can override for testing.
+ virtual void Start();
+
+ // UbertokenConsumer implementation:
+ virtual void OnUbertokenSuccess(const std::string& token) OVERRIDE;
+ virtual void OnUbertokenFailure(const GoogleServiceAuthError& error) OVERRIDE;
+
+ // WebAuthFlow::Delegate implementation.
+ virtual void OnAuthFlowFailure(WebAuthFlow::Failure failure) OVERRIDE;
+ virtual void OnAuthFlowURLChange(const GURL& redirect_url) OVERRIDE;
+ virtual void OnAuthFlowTitleChange(const std::string& title) OVERRIDE;
+
+ private:
+ // Creates a WebAuthFlow, which will navigate to |url|. Can override
+ // for testing. Used to kick off the MergeSession (step #2).
+ virtual scoped_ptr<WebAuthFlow> CreateWebAuthFlow(GURL url);
+
+ Delegate* delegate_;
+ Profile* profile_;
+ chrome::HostDesktopType host_desktop_type_;
+ std::string redirect_scheme_;
+ std::string redirect_path_prefix_;
+ GURL auth_url_;
+ scoped_ptr<UbertokenFetcher> ubertoken_fetcher_;
+ scoped_ptr<WebAuthFlow> web_flow_;
+
+ DISALLOW_COPY_AND_ASSIGN(GaiaWebAuthFlow);
+};
+
+} // extensions
+
+#endif // CHROME_BROWSER_EXTENSIONS_API_IDENTITY_GAIA_WEB_AUTH_FLOW_H_
diff --git a/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc b/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc
new file mode 100644
index 0000000..77c7fc04
--- /dev/null
+++ b/chrome/browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc
@@ -0,0 +1,235 @@
+// Copyright (c) 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/extensions/api/identity/gaia_web_auth_flow.h"
+
+#include <vector>
+
+#include "testing/gmock/include/gmock/gmock.h"
+#include "testing/gtest/include/gtest/gtest.h"
+
+namespace extensions {
+
+class FakeWebAuthFlow : public WebAuthFlow {
+ public:
+ explicit FakeWebAuthFlow(WebAuthFlow::Delegate* delegate)
+ : WebAuthFlow(delegate,
+ NULL,
+ GURL(),
+ WebAuthFlow::INTERACTIVE,
+ gfx::Rect(),
+ chrome::GetActiveDesktop()) {}
+
+ virtual void Start() OVERRIDE {}
+};
+
+class TestGaiaWebAuthFlow : public GaiaWebAuthFlow {
+ public:
+ TestGaiaWebAuthFlow(GaiaWebAuthFlow::Delegate* delegate,
+ const std::string& extension_id,
+ const OAuth2Info& oauth2_info,
+ GoogleServiceAuthError::State ubertoken_error_state)
+ : GaiaWebAuthFlow(delegate,
+ NULL,
+ chrome::GetActiveDesktop(),
+ "extension_id",
+ oauth2_info),
+ ubertoken_error_(ubertoken_error_state) {}
+
+ virtual void Start() OVERRIDE {
+ if (ubertoken_error_.state() == GoogleServiceAuthError::NONE)
+ OnUbertokenSuccess("fake_ubertoken");
+ else
+ OnUbertokenFailure(ubertoken_error_);
+ }
+
+ private:
+ virtual scoped_ptr<WebAuthFlow> CreateWebAuthFlow(GURL url) OVERRIDE {
+ return scoped_ptr<WebAuthFlow>(new FakeWebAuthFlow(this));
+ }
+
+ GoogleServiceAuthError ubertoken_error_;
+};
+
+class MockGaiaWebAuthFlowDelegate : public GaiaWebAuthFlow::Delegate {
+ public:
+ MOCK_METHOD3(OnGaiaFlowFailure,
+ void(GaiaWebAuthFlow::Failure failure,
+ GoogleServiceAuthError service_error,
+ const std::string& oauth_error));
+ MOCK_METHOD2(OnGaiaFlowCompleted,
+ void(const std::string& access_token,
+ const std::string& expiration));
+};
+
+class IdentityGaiaWebAuthFlowTest : public testing::Test {
+ public:
+ IdentityGaiaWebAuthFlowTest()
+ : ubertoken_error_state_(GoogleServiceAuthError::NONE) {}
+
+ scoped_ptr<TestGaiaWebAuthFlow> CreateTestFlow() {
+ OAuth2Info oauth2_info;
+ oauth2_info.client_id = "fake.client.id";
+ return scoped_ptr<TestGaiaWebAuthFlow>(new TestGaiaWebAuthFlow(
+ &delegate_, "extension_id", oauth2_info, ubertoken_error_state_));
+
+ }
+
+ std::string GetFinalTitle(const std::string& fragment) {
+ return std::string("Loading id.client.fake:/extension_id#") + fragment;
+ }
+
+ GoogleServiceAuthError GetNoneServiceError() {
+ return GoogleServiceAuthError(GoogleServiceAuthError::NONE);
+ }
+
+ void set_ubertoken_error(
+ GoogleServiceAuthError::State ubertoken_error_state) {
+ ubertoken_error_state_ = ubertoken_error_state;
+ }
+
+ protected:
+ testing::StrictMock<MockGaiaWebAuthFlowDelegate> delegate_;
+ GoogleServiceAuthError::State ubertoken_error_state_;
+};
+
+TEST_F(IdentityGaiaWebAuthFlowTest, OAuthError) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(delegate_, OnGaiaFlowFailure(
+ GaiaWebAuthFlow::OAUTH_ERROR,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ "access_denied"));
+ flow->OnAuthFlowTitleChange(GetFinalTitle("error=access_denied"));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, Token) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(delegate_, OnGaiaFlowCompleted("fake_access_token", ""));
+ flow->OnAuthFlowTitleChange(GetFinalTitle("access_token=fake_access_token"));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, TokenAndExpiration) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(delegate_, OnGaiaFlowCompleted("fake_access_token", "3600"));
+ flow->OnAuthFlowTitleChange(
+ GetFinalTitle("access_token=fake_access_token&expires_in=3600"));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, ExtraFragmentParametersSuccess) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(delegate_,
+ OnGaiaFlowCompleted("fake_access_token", "3600"));
+ flow->OnAuthFlowTitleChange(GetFinalTitle("chaff1=stuff&"
+ "expires_in=3600&"
+ "chaff2=and&"
+ "nonerror=fake_error&"
+ "chaff3=nonsense&"
+ "access_token=fake_access_token&"
+ "chaff4="));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, ExtraFragmentParametersError) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(delegate_, OnGaiaFlowFailure(
+ GaiaWebAuthFlow::OAUTH_ERROR,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ "fake_error"));
+ flow->OnAuthFlowTitleChange(GetFinalTitle("chaff1=stuff&"
+ "expires_in=3600&"
+ "chaff2=and&"
+ "error=fake_error&"
+ "chaff3=nonsense&"
+ "access_token=fake_access_token&"
+ "chaff4="));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, TitleSpam) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ flow->OnAuthFlowTitleChange(
+ "Loading https://extension_id.chromiumapp.org/#error=non_final_title");
+ flow->OnAuthFlowTitleChange("I'm feeling entitled.");
+ flow->OnAuthFlowTitleChange("");
+ flow->OnAuthFlowTitleChange(
+ "Loading id.client.fake:/bad_extension_id#error=non_final_title");
+ flow->OnAuthFlowTitleChange(
+ "Loading bad.id.client.fake:/extension_id#error=non_final_title");
+ EXPECT_CALL(delegate_, OnGaiaFlowCompleted("fake_access_token", ""));
+ flow->OnAuthFlowTitleChange(GetFinalTitle("access_token=fake_access_token"));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, EmptyFragment) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(
+ delegate_,
+ OnGaiaFlowFailure(
+ GaiaWebAuthFlow::INVALID_REDIRECT,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ ""));
+ flow->OnAuthFlowTitleChange(GetFinalTitle(""));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, JunkFragment) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(
+ delegate_,
+ OnGaiaFlowFailure(
+ GaiaWebAuthFlow::INVALID_REDIRECT,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ ""));
+ flow->OnAuthFlowTitleChange(GetFinalTitle("thisisjustabunchofjunk"));
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, NoFragment) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ // This won't be recognized as an interesting title.
+ flow->OnAuthFlowTitleChange("Loading id.client.fake:/extension_id");
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, Host) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ // These won't be recognized as interesting titles.
+ flow->OnAuthFlowTitleChange(
+ "Loading id.client.fake://extension_id#access_token=fake_access_token");
+ flow->OnAuthFlowTitleChange(
+ "Loading id.client.fake://extension_id/#access_token=fake_access_token");
+ flow->OnAuthFlowTitleChange(
+ "Loading "
+ "id.client.fake://host/extension_id/#access_token=fake_access_token");
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, UbertokenFailure) {
+ set_ubertoken_error(GoogleServiceAuthError::CONNECTION_FAILED);
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ EXPECT_CALL(
+ delegate_,
+ OnGaiaFlowFailure(
+ GaiaWebAuthFlow::SERVICE_AUTH_ERROR,
+ GoogleServiceAuthError(GoogleServiceAuthError::CONNECTION_FAILED),
+ ""));
+ flow->Start();
+}
+
+TEST_F(IdentityGaiaWebAuthFlowTest, AuthFlowFailure) {
+ scoped_ptr<TestGaiaWebAuthFlow> flow = CreateTestFlow();
+ flow->Start();
+ EXPECT_CALL(
+ delegate_,
+ OnGaiaFlowFailure(
+ GaiaWebAuthFlow::WINDOW_CLOSED,
+ GoogleServiceAuthError(GoogleServiceAuthError::NONE),
+ ""));
+ flow->OnAuthFlowFailure(WebAuthFlow::WINDOW_CLOSED);
+}
+
+} // namespace extensions
diff --git a/chrome/browser/extensions/api/identity/identity_api.cc b/chrome/browser/extensions/api/identity/identity_api.cc
index bb74eb4..178e6f7 100644
--- a/chrome/browser/extensions/api/identity/identity_api.cc
+++ b/chrome/browser/extensions/api/identity/identity_api.cc
@@ -10,6 +10,7 @@
#include "base/lazy_instance.h"
#include "base/stringprintf.h"
+#include "base/strings/string_number_conversions.h"
#include "base/values.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/extensions/extension_function_dispatcher.h"
@@ -224,7 +225,6 @@ void IdentityGetAuthTokenFunction::StartMintToken(
CompleteMintTokenFlow();
CompleteFunctionWithResult(cache_entry.token());
} else {
- install_ui_.reset(new ExtensionInstallPrompt(GetAssociatedWebContents()));
ShowOAuthApprovalDialog(issue_advice_);
}
}
@@ -291,15 +291,55 @@ void IdentityGetAuthTokenFunction::SigninFailed() {
CompleteFunctionWithError(identity_constants::kUserNotSignedIn);
}
-void IdentityGetAuthTokenFunction::InstallUIProceed() {
- // The user has accepted the scopes, so we may now force (recording a grant
- // and receiving a token).
- StartGaiaRequest(OAuth2MintTokenFlow::MODE_MINT_TOKEN_FORCE);
+void IdentityGetAuthTokenFunction::OnGaiaFlowFailure(
+ GaiaWebAuthFlow::Failure failure,
+ GoogleServiceAuthError service_error,
+ const std::string& oauth_error) {
+ CompleteMintTokenFlow();
+ std::string error;
+
+ switch (failure) {
+ case GaiaWebAuthFlow::WINDOW_CLOSED:
+ error = identity_constants::kUserRejected;
+ break;
+
+ case GaiaWebAuthFlow::INVALID_REDIRECT:
+ error = identity_constants::kInvalidRedirect;
+ break;
+
+ case GaiaWebAuthFlow::SERVICE_AUTH_ERROR:
+ error = std::string(identity_constants::kAuthFailure) +
+ service_error.ToString();
+ break;
+
+ case GaiaWebAuthFlow::OAUTH_ERROR:
+ error = MapOAuth2ErrorToDescription(oauth_error);
+ break;
+
+ default:
+ NOTREACHED() << "Unexpected error from gaia web auth flow: " << failure;
+ error = identity_constants::kInvalidRedirect;
+ break;
+ }
+
+ CompleteFunctionWithError(error);
}
-void IdentityGetAuthTokenFunction::InstallUIAbort(bool user_initiated) {
+void IdentityGetAuthTokenFunction::OnGaiaFlowCompleted(
+ const std::string& access_token,
+ const std::string& expiration) {
+
+ int time_to_live;
+ if (!expiration.empty() && base::StringToInt(expiration, &time_to_live)) {
+ const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
+ IdentityTokenCacheValue token_value(
+ access_token, base::TimeDelta::FromSeconds(time_to_live));
+ IdentityAPI::GetFactoryInstance()->GetForProfile(profile())
+ ->SetCachedToken(GetExtension()->id(), oauth2_info.scopes, token_value);
+ }
+
CompleteMintTokenFlow();
- CompleteFunctionWithError(identity_constants::kUserRejected);
+ CompleteFunctionWithResult(access_token);
}
void IdentityGetAuthTokenFunction::StartGaiaRequest(
@@ -315,7 +355,15 @@ void IdentityGetAuthTokenFunction::ShowLoginPopup() {
void IdentityGetAuthTokenFunction::ShowOAuthApprovalDialog(
const IssueAdviceInfo& issue_advice) {
- install_ui_->ConfirmIssueAdvice(this, GetExtension(), issue_advice);
+ Browser* current_browser = this->GetCurrentBrowser();
+ chrome::HostDesktopType host_desktop_type =
+ current_browser ? current_browser->host_desktop_type()
+ : chrome::GetActiveDesktop();
+ const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(GetExtension());
+
+ gaia_web_auth_flow_.reset(new GaiaWebAuthFlow(
+ this, profile(), host_desktop_type, GetExtension()->id(), oauth2_info));
+ gaia_web_auth_flow_->Start();
}
OAuth2MintTokenFlow* IdentityGetAuthTokenFunction::CreateMintTokenFlow(
@@ -350,6 +398,19 @@ bool IdentityGetAuthTokenFunction::HasLoginToken() const {
return token_service->HasOAuthLoginToken();
}
+std::string IdentityGetAuthTokenFunction::MapOAuth2ErrorToDescription(
+ const std::string& error) {
+ const char kOAuth2ErrorAccessDenied[] = "access_denied";
+ const char kOAuth2ErrorInvalidScope[] = "invalid_scope";
+
+ if (error == kOAuth2ErrorAccessDenied)
+ return std::string(identity_constants::kUserRejected);
+ else if (error == kOAuth2ErrorInvalidScope)
+ return std::string(identity_constants::kInvalidScopes);
+ else
+ return std::string(identity_constants::kAuthFailure) + error;
+}
+
IdentityRemoveCachedAuthTokenFunction::IdentityRemoveCachedAuthTokenFunction() {
}
diff --git a/chrome/browser/extensions/api/identity/identity_api.h b/chrome/browser/extensions/api/identity/identity_api.h
index 9fb6ddc..ed99871 100644
--- a/chrome/browser/extensions/api/identity/identity_api.h
+++ b/chrome/browser/extensions/api/identity/identity_api.h
@@ -11,23 +11,24 @@
#include "base/memory/ref_counted.h"
#include "base/memory/weak_ptr.h"
+#include "chrome/browser/extensions/api/identity/gaia_web_auth_flow.h"
#include "chrome/browser/extensions/api/identity/identity_mint_queue.h"
#include "chrome/browser/extensions/api/identity/identity_signin_flow.h"
#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
#include "chrome/browser/extensions/api/profile_keyed_api_factory.h"
#include "chrome/browser/extensions/extension_function.h"
-#include "chrome/browser/extensions/extension_install_prompt.h"
#include "chrome/browser/signin/signin_global_error.h"
#include "google_apis/gaia/oauth2_mint_token_flow.h"
-class GetAuthTokenFunctionTest;
-class MockGetAuthTokenFunction;
class GoogleServiceAuthError;
class Profile;
class SigninManagerBase;
namespace extensions {
+class GetAuthTokenFunctionTest;
+class MockGetAuthTokenFunction;
+
namespace identity_constants {
extern const char kInvalidClientId[];
extern const char kInvalidScopes[];
@@ -58,7 +59,7 @@ extern const char kOffTheRecord[];
// new login token, there is a sign-in flow. If that flow completes
// successfully, getAuthToken proceeds to the non-interactive flow.
class IdentityGetAuthTokenFunction : public AsyncExtensionFunction,
- public ExtensionInstallPrompt::Delegate,
+ public GaiaWebAuthFlow::Delegate,
public IdentityMintRequestQueue::Request,
public OAuth2MintTokenFlow::Delegate,
public IdentitySigninFlow::Delegate {
@@ -102,9 +103,12 @@ class IdentityGetAuthTokenFunction : public AsyncExtensionFunction,
virtual void SigninSuccess(const std::string& token) OVERRIDE;
virtual void SigninFailed() OVERRIDE;
- // ExtensionInstallPrompt::Delegate implementation:
- virtual void InstallUIProceed() OVERRIDE;
- virtual void InstallUIAbort(bool user_initiated) OVERRIDE;
+ // GaiaWebAuthFlow::Delegate implementation:
+ virtual void OnGaiaFlowFailure(GaiaWebAuthFlow::Failure failure,
+ GoogleServiceAuthError service_error,
+ const std::string& oauth_error) OVERRIDE;
+ virtual void OnGaiaFlowCompleted(const std::string& access_token,
+ const std::string& expiration) OVERRIDE;
// Starts a mint token request to GAIA.
void StartGaiaRequest(OAuth2MintTokenFlow::Mode mode);
@@ -119,6 +123,10 @@ class IdentityGetAuthTokenFunction : public AsyncExtensionFunction,
// Checks if there is a master login token to mint tokens for the extension.
virtual bool HasLoginToken() const;
+ // Maps OAuth2 protocol errors to an error message returned to the
+ // developer in chrome.runtime.lastError.
+ std::string MapOAuth2ErrorToDescription(const std::string& error);
+
bool should_prompt_for_scopes_;
IdentityMintRequestQueue::MintType mint_token_flow_type_;
scoped_ptr<OAuth2MintTokenFlow> mint_token_flow_;
@@ -128,7 +136,7 @@ class IdentityGetAuthTokenFunction : public AsyncExtensionFunction,
// When launched in interactive mode, and if there is no existing grant,
// a permissions prompt will be popped up to the user.
IssueAdviceInfo issue_advice_;
- scoped_ptr<ExtensionInstallPrompt> install_ui_;
+ scoped_ptr<GaiaWebAuthFlow> gaia_web_auth_flow_;
scoped_ptr<IdentitySigninFlow> signin_flow_;
};
@@ -163,6 +171,7 @@ class IdentityLaunchWebAuthFlowFunction : public AsyncExtensionFunction,
// WebAuthFlow::Delegate implementation.
virtual void OnAuthFlowFailure(WebAuthFlow::Failure failure) OVERRIDE;
virtual void OnAuthFlowURLChange(const GURL& redirect_url) OVERRIDE;
+ virtual void OnAuthFlowTitleChange(const std::string& title) OVERRIDE {}
// Helper to initialize final URL prefix.
void InitFinalRedirectURLPrefix(const std::string& extension_id);
diff --git a/chrome/browser/extensions/api/identity/identity_apitest.cc b/chrome/browser/extensions/api/identity/identity_apitest.cc
index ec97e685c..3dc060c 100644
--- a/chrome/browser/extensions/api/identity/identity_apitest.cc
+++ b/chrome/browser/extensions/api/identity/identity_apitest.cc
@@ -193,25 +193,32 @@ ProfileKeyedService* IdentityAPITestFactory(content::BrowserContext* profile) {
class MockGetAuthTokenFunction : public IdentityGetAuthTokenFunction {
public:
MockGetAuthTokenFunction() : login_ui_result_(true),
- install_ui_result_(false),
+ scope_ui_result_(true),
login_ui_shown_(false),
- install_ui_shown_(false) {
+ scope_ui_shown_(false) {
}
void set_login_ui_result(bool result) {
login_ui_result_ = result;
}
- void set_install_ui_result(bool result) {
- install_ui_result_ = result;
+ void set_scope_ui_failure(GaiaWebAuthFlow::Failure failure) {
+ scope_ui_result_ = false;
+ scope_ui_failure_ = failure;
+ }
+
+ void set_scope_ui_oauth_error(const std::string& oauth_error) {
+ scope_ui_result_ = false;
+ scope_ui_failure_ = GaiaWebAuthFlow::OAUTH_ERROR;
+ scope_ui_oauth_error_ = oauth_error;
}
bool login_ui_shown() const {
return login_ui_shown_;
}
- bool install_ui_shown() const {
- return install_ui_shown_;
+ bool scope_ui_shown() const {
+ return scope_ui_shown_;
}
virtual void ShowLoginPopup() OVERRIDE {
@@ -225,12 +232,17 @@ class MockGetAuthTokenFunction : public IdentityGetAuthTokenFunction {
virtual void ShowOAuthApprovalDialog(
const IssueAdviceInfo& issue_advice) OVERRIDE {
- install_ui_shown_ = true;
- // Call InstallUIProceed or InstallUIAbort based on the flag.
- if (install_ui_result_)
- InstallUIProceed();
- else
- InstallUIAbort(true);
+ scope_ui_shown_ = true;
+
+ if (scope_ui_result_) {
+ OnGaiaFlowCompleted(kAccessToken, "3600");
+ } else if (scope_ui_failure_ == GaiaWebAuthFlow::SERVICE_AUTH_ERROR) {
+ GoogleServiceAuthError error(GoogleServiceAuthError::CONNECTION_FAILED);
+ OnGaiaFlowFailure(scope_ui_failure_, error, "");
+ } else {
+ GoogleServiceAuthError error(GoogleServiceAuthError::NONE);
+ OnGaiaFlowFailure(scope_ui_failure_, error, scope_ui_oauth_error_);
+ }
}
MOCK_CONST_METHOD0(HasLoginToken, bool());
@@ -240,9 +252,11 @@ class MockGetAuthTokenFunction : public IdentityGetAuthTokenFunction {
private:
~MockGetAuthTokenFunction() {}
bool login_ui_result_;
- bool install_ui_result_;
+ bool scope_ui_result_;
+ GaiaWebAuthFlow::Failure scope_ui_failure_;
+ std::string scope_ui_oauth_error_;
bool login_ui_shown_;
- bool install_ui_shown_;
+ bool scope_ui_shown_;
};
class MockQueuedMintRequest : public IdentityMintRequestQueue::Request {
@@ -290,7 +304,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kInvalidClientId), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -301,7 +315,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kInvalidScopes), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -313,7 +327,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -329,7 +343,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_TRUE(StartsWithASCII(error, errors::kAuthFailure, false));
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -346,7 +360,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kNoGrant), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension);
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_ADVICE,
@@ -367,7 +381,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_TRUE(StartsWithASCII(error, errors::kAuthFailure, false));
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -387,7 +401,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
id_api()->GetCachedToken(extension->id(),
oauth2_info.scopes).status());
@@ -403,7 +417,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -420,7 +434,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -433,7 +447,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserNotSignedIn), error);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -450,7 +464,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_TRUE(StartsWithASCII(error, errors::kAuthFailure, false));
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -469,7 +483,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -482,65 +496,34 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
EXPECT_CALL(*func.get(), CreateMintTokenFlow(_)).WillOnce(Return(flow));
- func->set_install_ui_result(false);
+ func->set_scope_ui_failure(GaiaWebAuthFlow::WINDOW_CLOSED);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserRejected), error);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
-}
-
-IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
- InteractiveLoginSuccessApprovalDoneMintFailure) {
- scoped_refptr<MockGetAuthTokenFunction> func(new MockGetAuthTokenFunction());
- func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
- EXPECT_CALL(*func.get(), HasLoginToken())
- .WillOnce(Return(false));
- func->set_login_ui_result(true);
- TestOAuth2MintTokenFlow* flow1 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
- TestOAuth2MintTokenFlow* flow2 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::MINT_TOKEN_FAILURE, func.get());
- EXPECT_CALL(*func.get(), CreateMintTokenFlow(_))
- .WillOnce(Return(flow1))
- .WillOnce(Return(flow2));
-
- func->set_install_ui_result(true);
- std::string error = utils::RunFunctionAndReturnError(
- func.get(), "[{\"interactive\": true}]", browser());
- EXPECT_TRUE(StartsWithASCII(error, errors::kAuthFailure, false));
- EXPECT_TRUE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
- InteractiveLoginSuccessApprovalDoneMintSuccess) {
- scoped_refptr<MockGetAuthTokenFunction> func(new MockGetAuthTokenFunction());
+ InteractiveLoginSuccessApprovalSuccess) {
scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
+ scoped_refptr<MockGetAuthTokenFunction> func(new MockGetAuthTokenFunction());
func->set_extension(extension);
- const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension);
EXPECT_CALL(*func.get(), HasLoginToken())
.WillOnce(Return(false));
func->set_login_ui_result(true);
- TestOAuth2MintTokenFlow* flow1 = new TestOAuth2MintTokenFlow(
+ TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
- TestOAuth2MintTokenFlow* flow2 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get());
EXPECT_CALL(*func.get(), CreateMintTokenFlow(_))
- .WillOnce(Return(flow1))
- .WillOnce(Return(flow2));
+ .WillOnce(Return(flow));
- func->set_install_ui_result(true);
scoped_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"interactive\": true}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
- EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
- id_api()->GetCachedToken(extension->id(),
- oauth2_info.scopes).status());
+ EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -552,58 +535,102 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
EXPECT_CALL(*func.get(), CreateMintTokenFlow(_)).WillOnce(Return(flow));
- func->set_install_ui_result(false);
+ func->set_scope_ui_failure(GaiaWebAuthFlow::WINDOW_CLOSED);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_EQ(std::string(errors::kUserRejected), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
- InteractiveApprovalDoneMintSuccess) {
+ InteractiveApprovalInvalidRedirect) {
scoped_refptr<MockGetAuthTokenFunction> func(new MockGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
EXPECT_CALL(*func.get(), HasLoginToken())
.WillOnce(Return(true));
- TestOAuth2MintTokenFlow* flow1 = new TestOAuth2MintTokenFlow(
+ TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
- TestOAuth2MintTokenFlow* flow2 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get());
- EXPECT_CALL(*func.get(), CreateMintTokenFlow(_))
- .WillOnce(Return(flow1))
- .WillOnce(Return(flow2));
-
- func->set_install_ui_result(true);
- scoped_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
- func.get(), "[{\"interactive\": true}]", browser()));
- std::string access_token;
- EXPECT_TRUE(value->GetAsString(&access_token));
- EXPECT_EQ(std::string(kAccessToken), access_token);
+ EXPECT_CALL(*func.get(), CreateMintTokenFlow(_)).WillOnce(Return(flow));
+ func->set_scope_ui_failure(GaiaWebAuthFlow::INVALID_REDIRECT);
+ std::string error = utils::RunFunctionAndReturnError(
+ func.get(), "[{\"interactive\": true}]", browser());
+ EXPECT_EQ(std::string(errors::kInvalidRedirect), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
- InteractiveApprovalDoneMintBadCredentials) {
+ InteractiveApprovalConnectionFailure) {
scoped_refptr<MockGetAuthTokenFunction> func(new MockGetAuthTokenFunction());
func->set_extension(CreateExtension(CLIENT_ID | SCOPES));
EXPECT_CALL(*func.get(), HasLoginToken())
.WillOnce(Return(true));
- TestOAuth2MintTokenFlow* flow1 = new TestOAuth2MintTokenFlow(
+ TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
- TestOAuth2MintTokenFlow* flow2 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::MINT_TOKEN_BAD_CREDENTIALS, func.get());
- EXPECT_CALL(*func.get(), CreateMintTokenFlow(_))
- .WillOnce(Return(flow1))
- .WillOnce(Return(flow2));
-
- func->set_install_ui_result(true);
+ EXPECT_CALL(*func.get(), CreateMintTokenFlow(_)).WillOnce(Return(flow));
+ func->set_scope_ui_failure(GaiaWebAuthFlow::SERVICE_AUTH_ERROR);
std::string error = utils::RunFunctionAndReturnError(
func.get(), "[{\"interactive\": true}]", browser());
EXPECT_TRUE(StartsWithASCII(error, errors::kAuthFailure, false));
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
+}
+
+IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
+ InteractiveApprovalOAuthErrors) {
+ scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
+
+ std::map<std::string, std::string> error_map;
+ error_map.insert(std::make_pair("access_denied", errors::kUserRejected));
+ error_map.insert(std::make_pair("invalid_scope", errors::kInvalidScopes));
+ error_map.insert(std::make_pair(
+ "unmapped_error", std::string(errors::kAuthFailure) + "unmapped_error"));
+
+ for (std::map<std::string, std::string>::const_iterator
+ it = error_map.begin();
+ it != error_map.end();
+ ++it) {
+ scoped_refptr<MockGetAuthTokenFunction> func(
+ new MockGetAuthTokenFunction());
+ func->set_extension(extension);
+ EXPECT_CALL(*func.get(), HasLoginToken()).WillOnce(Return(true));
+ TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
+ TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
+ ON_CALL(*func.get(), CreateMintTokenFlow(_)).WillByDefault(Return(flow));
+ func->set_scope_ui_oauth_error(it->first);
+ std::string error = utils::RunFunctionAndReturnError(
+ func.get(), "[{\"interactive\": true}]", browser());
+ EXPECT_EQ(it->second, error);
+ EXPECT_FALSE(func->login_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
+ }
+}
+
+IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
+ InteractiveApprovalSuccess) {
+ scoped_refptr<const Extension> extension(CreateExtension(CLIENT_ID | SCOPES));
+ const OAuth2Info& oauth2_info = OAuth2Info::GetOAuth2Info(extension);
+ scoped_refptr<MockGetAuthTokenFunction> func(new MockGetAuthTokenFunction());
+ func->set_extension(extension);
+ EXPECT_CALL(*func.get(), HasLoginToken())
+ .WillOnce(Return(true));
+ TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
+ TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
+ EXPECT_CALL(*func.get(), CreateMintTokenFlow(_))
+ .WillOnce(Return(flow));
+
+ scoped_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
+ func.get(), "[{\"interactive\": true}]", browser()));
+ std::string access_token;
+ EXPECT_TRUE(value->GetAsString(&access_token));
+ EXPECT_EQ(std::string(kAccessToken), access_token);
+ EXPECT_FALSE(func->login_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
+
+ EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
+ id_api()->GetCachedToken(extension->id(),
+ oauth2_info.scopes).status());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoninteractiveQueue) {
@@ -645,7 +672,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, NoninteractiveQueue) {
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveQueue) {
@@ -677,15 +704,10 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveQueue) {
RunFunctionAsync(func, "[{\"interactive\": true}]");
// Verify that we have fetched the login token and run the first flow.
testing::Mock::VerifyAndClearExpectations(func);
- EXPECT_FALSE(func->install_ui_shown());
-
- // The UI will be displayed and the second flow will be created
- // after the first queued request clears.
- func->set_install_ui_result(true);
- TestOAuth2MintTokenFlow* flow2 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get());
- EXPECT_CALL(*func.get(), CreateMintTokenFlow(_)).WillOnce(Return(flow2));
+ EXPECT_FALSE(func->scope_ui_shown());
+ // The UI will be displayed and a token retrieved after the first
+ // queued request clears.
queue->RequestComplete(type, extension->id(), scopes, &queued_request);
scoped_ptr<base::Value> value(WaitForSingleResult(func));
@@ -693,7 +715,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest, InteractiveQueue) {
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -724,7 +746,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kNoGrant), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
queue->RequestComplete(type, extension->id(), scopes, &queued_request);
}
@@ -750,7 +772,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -772,7 +794,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
func.get(), "[{}]", browser());
EXPECT_EQ(std::string(errors::kNoGrant), error);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -816,7 +838,7 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_FALSE(func->login_ui_shown());
- EXPECT_FALSE(func->install_ui_shown());
+ EXPECT_FALSE(func->scope_ui_shown());
}
IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
@@ -836,22 +858,18 @@ IN_PROC_BROWSER_TEST_F(GetAuthTokenFunctionTest,
EXPECT_CALL(*func.get(), HasLoginToken())
.WillOnce(Return(false));
func->set_login_ui_result(true);
- TestOAuth2MintTokenFlow* flow1 = new TestOAuth2MintTokenFlow(
+ TestOAuth2MintTokenFlow* flow = new TestOAuth2MintTokenFlow(
TestOAuth2MintTokenFlow::ISSUE_ADVICE_SUCCESS, func.get());
- TestOAuth2MintTokenFlow* flow2 = new TestOAuth2MintTokenFlow(
- TestOAuth2MintTokenFlow::MINT_TOKEN_SUCCESS, func.get());
EXPECT_CALL(*func.get(), CreateMintTokenFlow(_))
- .WillOnce(Return(flow1))
- .WillOnce(Return(flow2));
+ .WillOnce(Return(flow));
- func->set_install_ui_result(true);
scoped_ptr<base::Value> value(utils::RunFunctionAndReturnSingleResult(
func.get(), "[{\"interactive\": true}]", browser()));
std::string access_token;
EXPECT_TRUE(value->GetAsString(&access_token));
EXPECT_EQ(std::string(kAccessToken), access_token);
EXPECT_TRUE(func->login_ui_shown());
- EXPECT_TRUE(func->install_ui_shown());
+ EXPECT_TRUE(func->scope_ui_shown());
EXPECT_EQ(IdentityTokenCacheValue::CACHE_STATUS_TOKEN,
id_api()->GetCachedToken(extension->id(),
oauth2_info.scopes).status());
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.cc b/chrome/browser/extensions/api/identity/web_auth_flow.cc
index dd76b47..3f2d945 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.cc
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.cc
@@ -5,11 +5,13 @@
#include "chrome/browser/extensions/api/identity/web_auth_flow.h"
#include "base/location.h"
+#include "base/utf_string_conversions.h"
#include "chrome/browser/profiles/profile.h"
#include "chrome/browser/ui/browser.h"
#include "chrome/browser/ui/browser_navigator.h"
#include "content/public/browser/load_notification_details.h"
#include "content/public/browser/navigation_controller.h"
+#include "content/public/browser/navigation_entry.h"
#include "content/public/browser/notification_details.h"
#include "content/public/browser/notification_source.h"
#include "content/public/browser/notification_types.h"
@@ -72,6 +74,10 @@ void WebAuthFlow::Start() {
this,
content::NOTIFICATION_RESOURCE_RECEIVED_REDIRECT,
content::Source<WebContents>(contents_));
+ registrar_.Add(
+ this,
+ content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED,
+ content::Source<WebContents>(contents_));
controller->LoadURL(
provider_url_,
@@ -129,6 +135,15 @@ void WebAuthFlow::Observe(int type,
BeforeUrlLoaded(redirect_details->new_url);
}
break;
+ case content::NOTIFICATION_WEB_CONTENTS_TITLE_UPDATED: {
+ std::pair<content::NavigationEntry*, bool>* title =
+ content::Details<
+ std::pair<content::NavigationEntry*, bool> >(details).ptr();
+
+ if (title->first)
+ delegate_->OnAuthFlowTitleChange(UTF16ToUTF8(title->first->GetTitle()));
+ }
+ break;
default:
NOTREACHED() << "Got a notification that we did not register for: "
<< type;
diff --git a/chrome/browser/extensions/api/identity/web_auth_flow.h b/chrome/browser/extensions/api/identity/web_auth_flow.h
index dd5eed8..882bef8 100644
--- a/chrome/browser/extensions/api/identity/web_auth_flow.h
+++ b/chrome/browser/extensions/api/identity/web_auth_flow.h
@@ -58,6 +58,8 @@ class WebAuthFlow : public content::NotificationObserver,
// Called on redirects and other navigations to see if the URL should stop
// the flow.
virtual void OnAuthFlowURLChange(const GURL& redirect_url) = 0;
+ // Called when the title of the current page changes.
+ virtual void OnAuthFlowTitleChange(const std::string& title) = 0;
protected:
virtual ~Delegate() {}
diff --git a/chrome/chrome_browser_extensions.gypi b/chrome/chrome_browser_extensions.gypi
index 5d39802..e08e6e9 100644
--- a/chrome/chrome_browser_extensions.gypi
+++ b/chrome/chrome_browser_extensions.gypi
@@ -235,6 +235,8 @@
'browser/extensions/api/identity/experimental_identity_api.h',
'browser/extensions/api/identity/experimental_web_auth_flow.cc',
'browser/extensions/api/identity/experimental_web_auth_flow.h',
+ 'browser/extensions/api/identity/gaia_web_auth_flow.cc',
+ 'browser/extensions/api/identity/gaia_web_auth_flow.h',
'browser/extensions/api/identity/identity_api.cc',
'browser/extensions/api/identity/identity_api.h',
'browser/extensions/api/identity/identity_mint_queue.cc',
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index ccd468c..f068cb1 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -733,6 +733,7 @@
'browser/extensions/api/extension_action/extension_browser_actions_api_unittest.cc',
'browser/extensions/api/file_system/file_system_api_unittest.cc',
'browser/extensions/api/identity/experimental_web_auth_flow_unittest.cc',
+ 'browser/extensions/api/identity/gaia_web_auth_flow_unittest.cc',
'browser/extensions/api/identity/identity_mint_queue_unittest.cc',
'browser/extensions/api/idle/idle_api_unittest.cc',
'browser/extensions/api/media_galleries_private/media_galleries_private_unittest.cc',
diff --git a/google_apis/gaia/gaia_urls.cc b/google_apis/gaia/gaia_urls.cc
index f5734c7..2135c31 100644
--- a/google_apis/gaia/gaia_urls.cc
+++ b/google_apis/gaia/gaia_urls.cc
@@ -31,6 +31,7 @@ const char kOAuthRevokeTokenUrlSuffix[] = "/AuthSubRevokeToken";
// API calls from accounts.google.com (LSO)
const char kGetOAuthTokenUrlSuffix[] = "/o/oauth/GetOAuthToken/";
const char kClientLoginToOAuth2UrlSuffix[] = "/o/oauth2/programmatic_auth";
+const char kOAuth2AuthUrlSuffix[] = "/o/oauth2/auth";
const char kOAuth2RevokeUrlSuffix[] = "/o/oauth2/revoke";
const char kOAuth2TokenUrlSuffix[] = "/o/oauth2/token";
const char kClientOAuthUrlSuffix[] = "/ClientOAuth";
@@ -116,6 +117,7 @@ GaiaUrls::GaiaUrls() {
get_oauth_token_url_ = lso_origin_url_ + kGetOAuthTokenUrlSuffix;
std::string client_login_to_oauth2_url = lso_origin_url_ +
kClientLoginToOAuth2UrlSuffix;
+ oauth2_auth_url_ = lso_origin_url_ + kOAuth2AuthUrlSuffix;
std::string oauth2_token_url = lso_origin_url_ + kOAuth2TokenUrlSuffix;
oauth2_revoke_url_ = lso_origin_url_ + kOAuth2RevokeUrlSuffix;
@@ -229,6 +231,10 @@ const std::string& GaiaUrls::client_login_to_oauth2_url() {
return client_login_to_oauth2_url_;
}
+const std::string& GaiaUrls::oauth2_auth_url() {
+ return oauth2_auth_url_;
+}
+
const std::string& GaiaUrls::oauth2_token_url() {
return oauth2_token_url_;
}
diff --git a/google_apis/gaia/gaia_urls.h b/google_apis/gaia/gaia_urls.h
index 3fd19bc..3b434e4b 100644
--- a/google_apis/gaia/gaia_urls.h
+++ b/google_apis/gaia/gaia_urls.h
@@ -38,6 +38,7 @@ class GaiaUrls {
const std::string& oauth2_chrome_client_id();
const std::string& oauth2_chrome_client_secret();
const std::string& client_login_to_oauth2_url();
+ const std::string& oauth2_auth_url();
const std::string& oauth2_token_url();
const std::string& oauth2_issue_token_url();
const std::string& oauth2_revoke_url();
@@ -75,6 +76,7 @@ class GaiaUrls {
std::string oauth2_chrome_client_id_;
std::string oauth2_chrome_client_secret_;
std::string client_login_to_oauth2_url_;
+ std::string oauth2_auth_url_;
std::string oauth2_token_url_;
std::string oauth2_issue_token_url_;
std::string oauth2_revoke_url_;