Commit 563594a4 authored by rockot@chromium.org's avatar rockot@chromium.org

Reland: Support multiple sign-in for extension updates.

This changes the ExtensionDownloader to attempt multiple
sequential queries for CRX downloads, iterating over incremental
values for the authuser query parameter up to a max of 10.

This is a re-landing of http://codereview.chromium.org/279453002/
which was reverted due to a use-after-free bug in the unit tests.

BUG=370964
R=asargent@chromium.org

Review URL: https://codereview.chromium.org/274263002

git-svn-id: svn://svn.chromium.org/chrome/trunk/src@269666 0039d316-1c4b-4281-b951-d872f2087c98
parent 9d7edfa9
...@@ -16,7 +16,9 @@ ...@@ -16,7 +16,9 @@
#include "base/metrics/sparse_histogram.h" #include "base/metrics/sparse_histogram.h"
#include "base/platform_file.h" #include "base/platform_file.h"
#include "base/stl_util.h" #include "base/stl_util.h"
#include "base/strings/string_number_conversions.h"
#include "base/strings/string_util.h" #include "base/strings/string_util.h"
#include "base/strings/stringprintf.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/version.h" #include "base/version.h"
#include "chrome/browser/chrome_notification_types.h" #include "chrome/browser/chrome_notification_types.h"
...@@ -74,6 +76,10 @@ const net::BackoffEntry::Policy kDefaultBackoffPolicy = { ...@@ -74,6 +76,10 @@ const net::BackoffEntry::Policy kDefaultBackoffPolicy = {
false, false,
}; };
const char kAuthUserQueryKey[] = "authuser";
const int kMaxAuthUserValue = 10;
const char kNotFromWebstoreInstallSource[] = "notfromwebstore"; const char kNotFromWebstoreInstallSource[] = "notfromwebstore";
const char kDefaultInstallSource[] = ""; const char kDefaultInstallSource[] = "";
...@@ -92,8 +98,66 @@ bool ShouldRetryRequest(const net::URLRequestStatus& status, ...@@ -92,8 +98,66 @@ bool ShouldRetryRequest(const net::URLRequestStatus& status,
int response_code) { int response_code) {
// Retry if the response code is a server error, or the request failed because // Retry if the response code is a server error, or the request failed because
// of network errors as opposed to file errors. // of network errors as opposed to file errors.
return (response_code >= 500 && status.is_success()) || return ((response_code >= 500 && status.is_success()) ||
status.status() == net::URLRequestStatus::FAILED; status.status() == net::URLRequestStatus::FAILED);
}
bool ShouldRetryRequestWithCookies(const net::URLRequestStatus& status,
int response_code,
bool included_cookies) {
if (included_cookies)
return false;
if (status.status() == net::URLRequestStatus::CANCELED)
return true;
// Retry if a 401 or 403 is received.
return (status.status() == net::URLRequestStatus::SUCCESS &&
(response_code == 401 || response_code == 403));
}
bool ShouldRetryRequestWithNextUser(const net::URLRequestStatus& status,
int response_code,
bool included_cookies) {
// Retry if a 403 is received in response to a request including cookies.
// Note that receiving a 401 in response to a request which included cookies
// should indicate that the |authuser| index was out of bounds for the profile
// and therefore Chrome should NOT retry with another index.
return (status.status() == net::URLRequestStatus::SUCCESS &&
response_code == 403 && included_cookies);
}
// This parses and updates a URL query such that the value of the |authuser|
// query parameter is incremented by 1. If parameter was not present in the URL,
// it will be added with a value of 1. All other query keys and values are
// preserved as-is. Returns |false| if the user index exceeds a hard-coded
// maximum.
bool IncrementAuthUserIndex(GURL* url) {
int user_index = 0;
std::string old_query = url->query();
std::vector<std::string> new_query_parts;
url::Component query(0, old_query.length());
url::Component key, value;
while (url::ExtractQueryKeyValue(old_query.c_str(), &query, &key, &value)) {
std::string key_string = old_query.substr(key.begin, key.len);
std::string value_string = old_query.substr(value.begin, value.len);
if (key_string == kAuthUserQueryKey) {
base::StringToInt(value_string, &user_index);
} else {
new_query_parts.push_back(base::StringPrintf(
"%s=%s", key_string.c_str(), value_string.c_str()));
}
}
if (user_index >= kMaxAuthUserValue)
return false;
new_query_parts.push_back(
base::StringPrintf("%s=%d", kAuthUserQueryKey, user_index + 1));
std::string new_query_string = JoinString(new_query_parts, '&');
url::Component new_query(0, new_query_string.size());
url::Replacements<char> replacements;
replacements.SetQuery(new_query_string.c_str(), new_query);
*url = url->ReplaceComponents(replacements);
return true;
} }
} // namespace } // namespace
...@@ -707,17 +771,23 @@ void ExtensionDownloader::OnCRXFetchComplete( ...@@ -707,17 +771,23 @@ void ExtensionDownloader::OnCRXFetchComplete(
} else { } else {
NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, true); NotifyDelegateDownloadFinished(fetch_data.Pass(), crx_path, true);
} }
} else if (status.status() == net::URLRequestStatus::SUCCESS && } else if (ShouldRetryRequestWithCookies(
(response_code == 401 || response_code == 403) && status,
!extensions_queue_.active_request()->is_protected) { response_code,
// On 401 or 403, requeue this fetch with cookies enabled. extensions_queue_.active_request()->is_protected)) {
// Requeue the fetch with |is_protected| set, enabling cookies.
extensions_queue_.active_request()->is_protected = true; extensions_queue_.active_request()->is_protected = true;
extensions_queue_.RetryRequest(backoff_delay); extensions_queue_.RetryRequest(backoff_delay);
} else if (ShouldRetryRequestWithNextUser(
status,
response_code,
extensions_queue_.active_request()->is_protected) &&
IncrementAuthUserIndex(&extensions_queue_.active_request()->url)) {
extensions_queue_.RetryRequest(backoff_delay);
} else { } else {
const std::set<int>& request_ids = const std::set<int>& request_ids =
extensions_queue_.active_request()->request_ids; extensions_queue_.active_request()->request_ids;
const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id]; const ExtensionDownloaderDelegate::PingResult& ping = ping_results_[id];
VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec() VLOG(1) << "Failed to fetch extension '" << url.possibly_invalid_spec()
<< "' response code:" << response_code; << "' response code:" << response_code;
if (ShouldRetryRequest(status, response_code) && if (ShouldRetryRequest(status, response_code) &&
......
...@@ -60,6 +60,7 @@ ...@@ -60,6 +60,7 @@
#include "net/url_request/url_request_status.h" #include "net/url_request/url_request_status.h"
#include "testing/gmock/include/gmock/gmock.h" #include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h" #include "testing/gtest/include/gtest/gtest.h"
#include "url/third_party/mozilla/url_parse.h"
#if defined(OS_CHROMEOS) #if defined(OS_CHROMEOS)
#include "chrome/browser/chromeos/login/user_manager.h" #include "chrome/browser/chromeos/login/user_manager.h"
...@@ -113,6 +114,8 @@ const net::BackoffEntry::Policy kNoBackoffPolicy = { ...@@ -113,6 +114,8 @@ const net::BackoffEntry::Policy kNoBackoffPolicy = {
const char kEmptyUpdateUrlData[] = ""; const char kEmptyUpdateUrlData[] = "";
const char kAuthUserQueryKey[] = "authuser";
int kExpectedLoadFlags = int kExpectedLoadFlags =
net::LOAD_DO_NOT_SEND_COOKIES | net::LOAD_DO_NOT_SEND_COOKIES |
net::LOAD_DO_NOT_SAVE_COOKIES | net::LOAD_DO_NOT_SAVE_COOKIES |
...@@ -248,6 +251,25 @@ class NotificationsObserver : public content::NotificationObserver { ...@@ -248,6 +251,25 @@ class NotificationsObserver : public content::NotificationObserver {
DISALLOW_COPY_AND_ASSIGN(NotificationsObserver); DISALLOW_COPY_AND_ASSIGN(NotificationsObserver);
}; };
// Extracts the integer value of the |authuser| query parameter. Returns 0 if
// the parameter is not set.
int GetAuthUserQueryValue(const GURL& url) {
std::string query_string = url.query();
url::Component query(0, query_string.length());
url::Component key, value;
while (
url::ExtractQueryKeyValue(query_string.c_str(), &query, &key, &value)) {
std::string key_string = query_string.substr(key.begin, key.len);
if (key_string == kAuthUserQueryKey) {
int user_index = 0;
base::StringToInt(query_string.substr(value.begin, value.len),
&user_index);
return user_index;
}
}
return 0;
}
} // namespace } // namespace
// Base class for further specialized test classes. // Base class for further specialized test classes.
...@@ -1100,7 +1122,10 @@ class ExtensionUpdaterTest : public testing::Test { ...@@ -1100,7 +1122,10 @@ class ExtensionUpdaterTest : public testing::Test {
// Update a single extension in an environment where the download request // Update a single extension in an environment where the download request
// initially responds with a 403 status. Expect the fetcher to automatically // initially responds with a 403 status. Expect the fetcher to automatically
// retry with cookies enabled. // retry with cookies enabled.
void TestSingleProtectedExtensionDownloading(bool use_https, bool fail) { void TestSingleProtectedExtensionDownloading(bool use_https,
bool fail,
int max_authuser,
int valid_authuser) {
net::TestURLFetcherFactory factory; net::TestURLFetcherFactory factory;
net::TestURLFetcher* fetcher = NULL; net::TestURLFetcher* fetcher = NULL;
scoped_ptr<ServiceForDownloadTests> service( scoped_ptr<ServiceForDownloadTests> service(
...@@ -1153,16 +1178,46 @@ class ExtensionUpdaterTest : public testing::Test { ...@@ -1153,16 +1178,46 @@ class ExtensionUpdaterTest : public testing::Test {
} }
// Attempt to fetch again after the auth failure. // Attempt to fetch again after the auth failure.
bool succeed = !fail;
if (fail) { if (fail) {
// Do not simulate incremental authuser retries.
if (max_authuser == 0) {
// Fail and verify that the fetch queue is cleared. // Fail and verify that the fetch queue is cleared.
fetcher->set_url(test_url); fetcher->set_url(test_url);
fetcher->set_status(net::URLRequestStatus()); fetcher->set_status(net::URLRequestStatus());
fetcher->set_response_code(403); fetcher->set_response_code(401);
fetcher->delegate()->OnURLFetchComplete(fetcher); fetcher->delegate()->OnURLFetchComplete(fetcher);
RunUntilIdle(); RunUntilIdle();
EXPECT_EQ(0U, updater.downloader_->extensions_queue_.active_request()); EXPECT_EQ(0U, updater.downloader_->extensions_queue_.active_request());
} else { return;
}
// Simulate incremental authuser retries.
for (int user_index = 0; user_index <= max_authuser; ++user_index) {
const ExtensionDownloader::ExtensionFetch& fetch =
*updater.downloader_->extensions_queue_.active_request();
EXPECT_EQ(user_index, GetAuthUserQueryValue(fetch.url));
if (user_index == valid_authuser) {
succeed = true;
break;
}
fetcher =
factory.GetFetcherByID(ExtensionDownloader::kExtensionFetcherId);
EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
fetcher->set_url(fetch.url);
fetcher->set_status(net::URLRequestStatus());
fetcher->set_response_code(403);
fetcher->delegate()->OnURLFetchComplete(fetcher);
RunUntilIdle();
}
}
// Succeed // Succeed
if (succeed) {
fetcher =
factory.GetFetcherByID(ExtensionDownloader::kExtensionFetcherId);
EXPECT_TRUE(fetcher != NULL && fetcher->delegate() != NULL);
base::FilePath extension_file_path(FILE_PATH_LITERAL("/whatever")); base::FilePath extension_file_path(FILE_PATH_LITERAL("/whatever"));
fetcher->set_url(test_url); fetcher->set_url(test_url);
fetcher->set_status(net::URLRequestStatus()); fetcher->set_status(net::URLRequestStatus());
...@@ -1595,16 +1650,31 @@ TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingFailurePending) { ...@@ -1595,16 +1650,31 @@ TEST_F(ExtensionUpdaterTest, TestSingleExtensionDownloadingFailurePending) {
TestSingleExtensionDownloading(true, false, true); TestSingleExtensionDownloading(true, false, true);
} }
TEST_F(ExtensionUpdaterTest, TestSingleProtectedExtensionDownloading) { TEST_F(ExtensionUpdaterTest, SingleProtectedExtensionDownloading) {
TestSingleProtectedExtensionDownloading(true, false); TestSingleProtectedExtensionDownloading(true, false, 0, 0);
}
TEST_F(ExtensionUpdaterTest, SingleProtectedExtensionDownloadingFailure) {
TestSingleProtectedExtensionDownloading(true, true, 0, 0);
}
TEST_F(ExtensionUpdaterTest, SingleProtectedExtensionDownloadingNoHTTPS) {
TestSingleProtectedExtensionDownloading(false, false, 0, 0);
}
TEST_F(ExtensionUpdaterTest,
SingleProtectedExtensionDownloadingWithNonDefaultAuthUser1) {
TestSingleProtectedExtensionDownloading(true, true, 2, 1);
} }
TEST_F(ExtensionUpdaterTest, TestSingleProtectedExtensionDownloadingFailure) { TEST_F(ExtensionUpdaterTest,
TestSingleProtectedExtensionDownloading(true, true); SingleProtectedExtensionDownloadingWithNonDefaultAuthUser2) {
TestSingleProtectedExtensionDownloading(true, true, 2, 2);
} }
TEST_F(ExtensionUpdaterTest, TestSingleProtectedExtensionDownloadingNoHTTPS) { TEST_F(ExtensionUpdaterTest,
TestSingleProtectedExtensionDownloading(false, false); SingleProtectedExtensionDownloadingAuthUserExhaustionFailure) {
TestSingleProtectedExtensionDownloading(true, true, 2, 5);
} }
TEST_F(ExtensionUpdaterTest, TestMultipleExtensionDownloadingUpdatesFail) { TEST_F(ExtensionUpdaterTest, TestMultipleExtensionDownloadingUpdatesFail) {
......
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