summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--chrome/browser/chromeos/login/profile_auth_data.cc171
-rw-r--r--chrome/browser/chromeos/login/profile_auth_data.h26
-rw-r--r--chrome/browser/chromeos/login/profile_auth_data_unittest.cc336
-rw-r--r--chrome/browser/chromeos/login/saml/saml_browsertest.cc281
-rw-r--r--chrome/browser/chromeos/login/session/user_session_manager.cc38
-rw-r--r--chrome/browser/chromeos/login/session/user_session_manager.h4
-rw-r--r--chrome/browser/chromeos/login/test/https_forwarder.py17
-rw-r--r--chrome/browser/chromeos/policy/device_policy_decoder_chromeos.cc12
-rw-r--r--chrome/browser/chromeos/policy/proto/chrome_device_policy.proto9
-rw-r--r--chrome/browser/chromeos/settings/device_settings_provider.cc9
-rw-r--r--chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc6
-rw-r--r--chrome/chrome_tests_unit.gypi1
-rw-r--r--chrome/test/data/policy/policy_test_cases.json3
-rw-r--r--chromeos/settings/cros_settings_names.cc2
-rw-r--r--chromeos/settings/cros_settings_names.h1
-rw-r--r--components/policy/resources/policy_templates.json24
-rw-r--r--google_apis/gaia/fake_gaia.cc4
-rw-r--r--tools/metrics/histograms/histograms.xml1
18 files changed, 826 insertions, 119 deletions
diff --git a/chrome/browser/chromeos/login/profile_auth_data.cc b/chrome/browser/chromeos/login/profile_auth_data.cc
index 8279856..e269ca2 100644
--- a/chrome/browser/chromeos/login/profile_auth_data.cc
+++ b/chrome/browser/chromeos/login/profile_auth_data.cc
@@ -4,12 +4,15 @@
#include "chrome/browser/chromeos/login/profile_auth_data.h"
+#include <string>
+
#include "base/bind.h"
#include "base/bind_helpers.h"
#include "base/callback.h"
#include "base/location.h"
#include "base/logging.h"
#include "base/memory/ref_counted.h"
+#include "base/message_loop/message_loop.h"
#include "content/public/browser/browser_context.h"
#include "content/public/browser/browser_thread.h"
#include "net/cookies/canonical_cookie.h"
@@ -22,6 +25,7 @@
#include "net/ssl/channel_id_store.h"
#include "net/url_request/url_request_context.h"
#include "net/url_request/url_request_context_getter.h"
+#include "url/gurl.h"
using content::BrowserThread;
@@ -29,12 +33,23 @@ namespace chromeos {
namespace {
+// Given a |cookie| set during login, returns true if the cookie may have been
+// set by GAIA. While GAIA can set cookies for many different domains, the
+// domain names it sets cookies for during Chrome OS login will always contain
+// the strings "google" or "youtube".
+bool IsGAIACookie(const net::CanonicalCookie& cookie) {
+ const std::string& domain = cookie.Domain();
+ return domain.find("google") != std::string::npos ||
+ domain.find("youtube") != std::string::npos;
+}
+
class ProfileAuthDataTransferer {
public:
ProfileAuthDataTransferer(
content::BrowserContext* from_context,
content::BrowserContext* to_context,
- bool transfer_auth_cookies_and_channel_ids,
+ bool transfer_auth_cookies_and_channel_ids_on_first_login,
+ bool transfer_saml_auth_cookies_on_subsequent_login,
const base::Closure& completion_callback);
void BeginTransfer();
@@ -47,6 +62,13 @@ class ProfileAuthDataTransferer {
// authentication information will be transferred into the user's session.
void TransferProxyAuthCache();
+ // Callback that receives the content of |to_context_|'s cookie jar. Checks
+ // whether this is the user's first login, based on the state of the cookie
+ // jar, and starts retrieval of the data that should be transfered. Calls
+ // Finish() if there is no data to transfer.
+ void OnTargetCookieJarContentsRetrieved(
+ const net::CookieList& target_cookies);
+
// Retrieve the contents of |from_context_|'s cookie jar. When the retrieval
// finishes, OnCookiesToTransferRetrieved will be called with the result.
void RetrieveCookiesToTransfer();
@@ -64,52 +86,53 @@ class ProfileAuthDataTransferer {
void OnChannelIDsToTransferRetrieved(
const net::ChannelIDStore::ChannelIDList& channel_ids_to_transfer);
- // If both auth cookies and channel IDs have been retrieved from
- // |from_context| already, retrieve the contents of |to_context|'s cookie jar
- // as well, allowing OnTargetCookieJarContentsRetrieved() to perform the
- // actual transfer.
+ // If all data to be transferred has been retrieved already, transfer it to
+ // |to_context_| and call Finish().
void MaybeTransferCookiesAndChannelIDs();
- // Transfer auth cookies and server bound certificates to the user's
- // |to_context_| if the user's cookie jar is empty. Call Finish() when done.
- void OnTargetCookieJarContentsRetrieved(
- const net::CookieList& target_cookies);
-
- // Post the |completion_callback_| to the UI thread and delete |this|.
+ // Post the |completion_callback_| to the UI thread and schedule destruction
+ // of |this|.
void Finish();
scoped_refptr<net::URLRequestContextGetter> from_context_;
scoped_refptr<net::URLRequestContextGetter> to_context_;
- bool transfer_auth_cookies_and_channel_ids_;
+ bool transfer_auth_cookies_and_channel_ids_on_first_login_;
+ bool transfer_saml_auth_cookies_on_subsequent_login_;
base::Closure completion_callback_;
net::CookieList cookies_to_transfer_;
net::ChannelIDStore::ChannelIDList channel_ids_to_transfer_;
- bool got_cookies_;
- bool got_channel_ids_;
+ bool first_login_;
+ bool waiting_for_auth_cookies_;
+ bool waiting_for_channel_ids_;
};
ProfileAuthDataTransferer::ProfileAuthDataTransferer(
content::BrowserContext* from_context,
content::BrowserContext* to_context,
- bool transfer_auth_cookies_and_channel_ids,
+ bool transfer_auth_cookies_and_channel_ids_on_first_login,
+ bool transfer_saml_auth_cookies_on_subsequent_login,
const base::Closure& completion_callback)
: from_context_(from_context->GetRequestContext()),
to_context_(to_context->GetRequestContext()),
- transfer_auth_cookies_and_channel_ids_(
- transfer_auth_cookies_and_channel_ids),
+ transfer_auth_cookies_and_channel_ids_on_first_login_(
+ transfer_auth_cookies_and_channel_ids_on_first_login),
+ transfer_saml_auth_cookies_on_subsequent_login_(
+ transfer_saml_auth_cookies_on_subsequent_login),
completion_callback_(completion_callback),
- got_cookies_(false),
- got_channel_ids_(false) {
+ first_login_(false),
+ waiting_for_auth_cookies_(false),
+ waiting_for_channel_ids_(false) {
}
void ProfileAuthDataTransferer::BeginTransfer() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- // If we aren't transferring auth cookies or server bound certificates, post
- // the completion callback immediately. Otherwise, it will be called when both
- // auth cookies and server bound certificates have been transferred.
- if (!transfer_auth_cookies_and_channel_ids_) {
+ // If we aren't transferring auth cookies or channel IDs, post the completion
+ // callback immediately. Otherwise, it will be called when the transfer
+ // finishes.
+ if (!transfer_auth_cookies_and_channel_ids_on_first_login_ &&
+ !transfer_saml_auth_cookies_on_subsequent_login_) {
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, completion_callback_);
// Null the callback so that when Finish is called, the callback won't be
// called again.
@@ -124,9 +147,16 @@ void ProfileAuthDataTransferer::BeginTransfer() {
void ProfileAuthDataTransferer::BeginTransferOnIOThread() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
TransferProxyAuthCache();
- if (transfer_auth_cookies_and_channel_ids_) {
- RetrieveCookiesToTransfer();
- RetrieveChannelIDsToTransfer();
+ if (transfer_auth_cookies_and_channel_ids_on_first_login_ ||
+ transfer_saml_auth_cookies_on_subsequent_login_) {
+ // Retrieve the contents of |to_context_|'s cookie jar.
+ net::CookieStore* to_store =
+ to_context_->GetURLRequestContext()->cookie_store();
+ net::CookieMonster* to_monster = to_store->GetCookieMonster();
+ to_monster->GetAllCookiesAsync(
+ base::Bind(
+ &ProfileAuthDataTransferer::OnTargetCookieJarContentsRetrieved,
+ base::Unretained(this)));
} else {
Finish();
}
@@ -140,6 +170,34 @@ void ProfileAuthDataTransferer::TransferProxyAuthCache() {
http_transaction_factory()->GetSession()->http_auth_cache());
}
+void ProfileAuthDataTransferer::OnTargetCookieJarContentsRetrieved(
+ const net::CookieList& target_cookies) {
+ DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
+ first_login_ = target_cookies.empty();
+ if (first_login_) {
+ // On first login, transfer all auth cookies and channel IDs if
+ // |transfer_auth_cookies_and_channel_ids_on_first_login_| is true.
+ waiting_for_auth_cookies_ =
+ transfer_auth_cookies_and_channel_ids_on_first_login_;
+ waiting_for_channel_ids_ =
+ transfer_auth_cookies_and_channel_ids_on_first_login_;
+ } else {
+ // On subsequent login, transfer auth cookies set by the SAML IdP if
+ // |transfer_saml_auth_cookies_on_subsequent_login_| is true.
+ waiting_for_auth_cookies_ = transfer_saml_auth_cookies_on_subsequent_login_;
+ }
+
+ if (!waiting_for_auth_cookies_ && !waiting_for_channel_ids_) {
+ Finish();
+ return;
+ }
+
+ if (waiting_for_auth_cookies_)
+ RetrieveCookiesToTransfer();
+ if (waiting_for_channel_ids_)
+ RetrieveChannelIDsToTransfer();
+}
+
void ProfileAuthDataTransferer::RetrieveCookiesToTransfer() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
net::CookieStore* from_store =
@@ -154,7 +212,7 @@ void ProfileAuthDataTransferer::RetrieveCookiesToTransfer() {
void ProfileAuthDataTransferer::OnCookiesToTransferRetrieved(
const net::CookieList& cookies_to_transfer) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- got_cookies_ = true;
+ waiting_for_auth_cookies_ = false;
cookies_to_transfer_ = cookies_to_transfer;
MaybeTransferCookiesAndChannelIDs();
}
@@ -173,42 +231,44 @@ void ProfileAuthDataTransferer::OnChannelIDsToTransferRetrieved(
const net::ChannelIDStore::ChannelIDList& channel_ids_to_transfer) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
channel_ids_to_transfer_ = channel_ids_to_transfer;
- got_channel_ids_ = true;
+ waiting_for_channel_ids_ = false;
MaybeTransferCookiesAndChannelIDs();
}
void ProfileAuthDataTransferer::MaybeTransferCookiesAndChannelIDs() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- if (!(got_cookies_ && got_channel_ids_))
+ if (waiting_for_auth_cookies_ || waiting_for_channel_ids_)
return;
- // Nothing to transfer over?
- if (!cookies_to_transfer_.size()) {
- Finish();
- return;
- }
-
- // Retrieve the contents of |to_context_|'s cookie jar.
net::CookieStore* to_store =
to_context_->GetURLRequestContext()->cookie_store();
net::CookieMonster* to_monster = to_store->GetCookieMonster();
- to_monster->GetAllCookiesAsync(
- base::Bind(&ProfileAuthDataTransferer::OnTargetCookieJarContentsRetrieved,
- base::Unretained(this)));
-}
-
-void ProfileAuthDataTransferer::OnTargetCookieJarContentsRetrieved(
- const net::CookieList& target_cookies) {
- DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
- if (target_cookies.empty()) {
- net::CookieStore* to_store =
- to_context_->GetURLRequestContext()->cookie_store();
- net::CookieMonster* to_monster = to_store->GetCookieMonster();
+ if (first_login_) {
to_monster->InitializeFrom(cookies_to_transfer_);
net::ChannelIDService* to_cert_service =
to_context_->GetURLRequestContext()->channel_id_service();
to_cert_service->GetChannelIDStore()->InitializeFrom(
channel_ids_to_transfer_);
+ } else {
+ for (net::CookieList::const_iterator it = cookies_to_transfer_.begin();
+ it != cookies_to_transfer_.end(); ++it) {
+ if (IsGAIACookie(*it))
+ continue;
+ // Although this method can be asynchronous, it will run synchronously in
+ // this case as the target cookie jar is guaranteed to be loaded and
+ // ready.
+ to_monster->SetCookieWithDetailsAsync(
+ GURL(it->Source()),
+ it->Name(),
+ it->Value(),
+ it->Domain(),
+ it->Path(),
+ it->ExpiryDate(),
+ it->IsSecure(),
+ it->IsHttpOnly(),
+ it->Priority(),
+ net::CookieStore::SetCookiesCallback());
+ }
}
Finish();
@@ -218,7 +278,7 @@ void ProfileAuthDataTransferer::Finish() {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::IO));
if (!completion_callback_.is_null())
BrowserThread::PostTask(BrowserThread::UI, FROM_HERE, completion_callback_);
- delete this;
+ base::MessageLoop::current()->DeleteSoon(FROM_HERE, this);
}
} // namespace
@@ -226,13 +286,16 @@ void ProfileAuthDataTransferer::Finish() {
void ProfileAuthData::Transfer(
content::BrowserContext* from_context,
content::BrowserContext* to_context,
- bool transfer_auth_cookies_and_channel_ids,
+ bool transfer_auth_cookies_and_channel_ids_on_first_login,
+ bool transfer_saml_auth_cookies_on_subsequent_login,
const base::Closure& completion_callback) {
DCHECK(BrowserThread::CurrentlyOn(BrowserThread::UI));
- (new ProfileAuthDataTransferer(from_context,
- to_context,
- transfer_auth_cookies_and_channel_ids,
- completion_callback))->BeginTransfer();
+ (new ProfileAuthDataTransferer(
+ from_context,
+ to_context,
+ transfer_auth_cookies_and_channel_ids_on_first_login,
+ transfer_saml_auth_cookies_on_subsequent_login,
+ completion_callback))->BeginTransfer();
}
} // namespace chromeos
diff --git a/chrome/browser/chromeos/login/profile_auth_data.h b/chrome/browser/chromeos/login/profile_auth_data.h
index 9d7c8c0..59a35a2 100644
--- a/chrome/browser/chromeos/login/profile_auth_data.h
+++ b/chrome/browser/chromeos/login/profile_auth_data.h
@@ -20,16 +20,22 @@ class ProfileAuthData {
public:
// Transfers authentication-related data from |from_context| to |to_context|
// and invokes |completion_callback| on the UI thread when the operation has
- // completed. The proxy authentication state is transferred unconditionally.
- // If |transfer_auth_cookies_and_channel_ids| is true, authentication
- // cookies and channel ids are transferred as well, if
- // |to_context|'s cookie jar is empty. If the cookie jar is not empty, the
- // authentication states in |from_context| and |to_context| should be merged
- // using /MergeSession instead.
- static void Transfer(content::BrowserContext* from_context,
- content::BrowserContext* to_context,
- bool transfer_auth_cookies_and_channel_ids,
- const base::Closure& completion_callback);
+ // completed. The following data is transferred:
+ // * The proxy authentication state.
+ // * All authentication cookies and channel IDs, if
+ // |transfer_auth_cookies_and_channel_ids_on_first_login| is true and
+ // |to_context|'s cookie jar is empty. If the cookie jar is not empty, the
+ // authentication states in |from_context| and |to_context| should be merged
+ // using /MergeSession instead.
+ // * The authentication cookies set by a SAML IdP, if
+ // |transfer_saml_auth_cookies_on_subsequent_login| is true and
+ // |to_context|'s cookie jar is not empty.
+ static void Transfer(
+ content::BrowserContext* from_context,
+ content::BrowserContext* to_context,
+ bool transfer_auth_cookies_and_channel_ids_on_first_login,
+ bool transfer_saml_auth_cookies_on_subsequent_login,
+ const base::Closure& completion_callback);
private:
DISALLOW_IMPLICIT_CONSTRUCTORS(ProfileAuthData);
diff --git a/chrome/browser/chromeos/login/profile_auth_data_unittest.cc b/chrome/browser/chromeos/login/profile_auth_data_unittest.cc
new file mode 100644
index 0000000..272d603
--- /dev/null
+++ b/chrome/browser/chromeos/login/profile_auth_data_unittest.cc
@@ -0,0 +1,336 @@
+// Copyright 2014 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/chromeos/login/profile_auth_data.h"
+
+#include <string>
+
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/compiler_specific.h"
+#include "base/memory/scoped_ptr.h"
+#include "base/run_loop.h"
+#include "base/strings/string16.h"
+#include "base/strings/utf_string_conversions.h"
+#include "base/time/time.h"
+#include "chrome/test/base/testing_profile.h"
+#include "content/public/browser/browser_context.h"
+#include "content/public/test/test_browser_thread_bundle.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_constants.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store.h"
+#include "net/http/http_auth.h"
+#include "net/http/http_auth_cache.h"
+#include "net/http/http_network_session.h"
+#include "net/http/http_transaction_factory.h"
+#include "net/ssl/channel_id_service.h"
+#include "net/ssl/channel_id_store.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
+#include "testing/gtest/include/gtest/gtest.h"
+#include "url/gurl.h"
+
+namespace chromeos {
+
+namespace {
+
+const char kProxyAuthURL[] = "http://example.com/";
+const char kProxyAuthRealm[] = "realm";
+const char kProxyAuthChallenge[] = "challenge";
+const char kProxyAuthPassword1[] = "password 1";
+const char kProxyAuthPassword2[] = "password 2";
+
+const char kGAIACookieURL[] = "http://google.com/";
+const char kSAMLIdPCookieURL[] = "http://example.com/";
+const char kCookieName[] = "cookie";
+const char kCookieValue1[] = "value 1";
+const char kCookieValue2[] = "value 2";
+const char kGAIACookieDomain[] = ".google.com";
+const char kSAMLIdPCookieDomain[] = ".example.com";
+
+const char kChannelIDServerIdentifier[] = "server";
+const char kChannelIDPrivateKey1[] = "private key 1";
+const char kChannelIDPrivateKey2[] = "private key 2";
+const char kChannelIDCert1[] = "cert 1";
+const char kChannelIDCert2[] = "cert 2";
+
+} // namespace
+
+class ProfileAuthDataTest : public testing::Test {
+ public:
+ // testing::Test:
+ virtual void SetUp() OVERRIDE;
+
+ void PopulateUserBrowserContext();
+
+ void Transfer(
+ bool transfer_auth_cookies_and_channel_ids_on_first_login,
+ bool transfer_saml_auth_cookies_on_subsequent_login);
+
+ net::CookieList GetUserCookies();
+ net::ChannelIDStore::ChannelIDList GetUserChannelIDs();
+
+ void VerifyTransferredUserProxyAuthEntry();
+ void VerifyUserCookies(const std::string& expected_gaia_cookie_value,
+ const std::string& expected_saml_idp_cookie_value);
+ void VerifyUserChannelID(const std::string& expected_private_key,
+ const std::string& expected_cert);
+
+ private:
+ void PopulateBrowserContext(content::BrowserContext* browser_context,
+ const std::string& proxy_auth_password,
+ const std::string& cookie_value,
+ const std::string& channel_id_private_key,
+ const std::string& channel_id_cert);
+
+ net::URLRequestContext* GetRequestContext(
+ content::BrowserContext* browser_context);
+ net::HttpAuthCache* GetProxyAuth(content::BrowserContext* browser_context);
+ net::CookieMonster* GetCookies(content::BrowserContext* browser_context);
+ net::ChannelIDStore* GetChannelIDs(content::BrowserContext* browser_context);
+
+ void QuitLoop(bool ignored);
+ void StoreCookieListAndQuitLoop(const net::CookieList& cookie_list);
+ void StoreChannelIDListAndQuitLoop(
+ const net::ChannelIDStore::ChannelIDList& channel_id_list);
+
+ content::TestBrowserThreadBundle thread_bundle_;
+
+ TestingProfile login_browser_context_;
+ TestingProfile user_browser_context_;
+
+ net::CookieList user_cookie_list_;
+ net::ChannelIDStore::ChannelIDList user_channel_id_list_;
+
+ scoped_ptr<base::RunLoop> run_loop_;
+};
+
+void ProfileAuthDataTest::SetUp() {
+ PopulateBrowserContext(&login_browser_context_,
+ kProxyAuthPassword1,
+ kCookieValue1,
+ kChannelIDPrivateKey1,
+ kChannelIDCert1);
+}
+
+void ProfileAuthDataTest::PopulateUserBrowserContext() {
+ PopulateBrowserContext(&user_browser_context_,
+ kProxyAuthPassword2,
+ kCookieValue2,
+ kChannelIDPrivateKey2,
+ kChannelIDCert2);
+}
+
+void ProfileAuthDataTest::Transfer(
+ bool transfer_auth_cookies_and_channel_ids_on_first_login,
+ bool transfer_saml_auth_cookies_on_subsequent_login) {
+ base::RunLoop run_loop;
+ ProfileAuthData::Transfer(
+ &login_browser_context_,
+ &user_browser_context_,
+ transfer_auth_cookies_and_channel_ids_on_first_login,
+ transfer_saml_auth_cookies_on_subsequent_login,
+ run_loop.QuitClosure());
+ run_loop.Run();
+ if (!transfer_auth_cookies_and_channel_ids_on_first_login &&
+ !transfer_saml_auth_cookies_on_subsequent_login) {
+ // When only proxy auth state is being transferred, the completion callback
+ // is invoked before the transfer has actually completed. Spin the loop once
+ // more to allow the transfer to complete.
+ base::RunLoop().RunUntilIdle();
+ }
+}
+
+net::CookieList ProfileAuthDataTest::GetUserCookies() {
+ run_loop_.reset(new base::RunLoop);
+ GetCookies(&user_browser_context_)->GetAllCookiesAsync(base::Bind(
+ &ProfileAuthDataTest::StoreCookieListAndQuitLoop,
+ base::Unretained(this)));
+ run_loop_->Run();
+ return user_cookie_list_;
+}
+
+net::ChannelIDStore::ChannelIDList ProfileAuthDataTest::GetUserChannelIDs() {
+ run_loop_.reset(new base::RunLoop);
+ GetChannelIDs(&user_browser_context_)->GetAllChannelIDs(base::Bind(
+ &ProfileAuthDataTest::StoreChannelIDListAndQuitLoop,
+ base::Unretained(this)));
+ run_loop_->Run();
+ return user_channel_id_list_;
+}
+
+void ProfileAuthDataTest::VerifyTransferredUserProxyAuthEntry() {
+ net::HttpAuthCache::Entry* entry =
+ GetProxyAuth(&user_browser_context_)->Lookup(
+ GURL(kProxyAuthURL),
+ kProxyAuthRealm,
+ net::HttpAuth::AUTH_SCHEME_BASIC);
+ ASSERT_TRUE(entry);
+ EXPECT_EQ(base::ASCIIToUTF16(kProxyAuthPassword1),
+ entry->credentials().password());
+}
+
+void ProfileAuthDataTest::VerifyUserCookies(
+ const std::string& expected_gaia_cookie_value,
+ const std::string& expected_saml_idp_cookie_value) {
+ net::CookieList user_cookies = GetUserCookies();
+ ASSERT_EQ(2u, user_cookies.size());
+ net::CanonicalCookie* cookie = &user_cookies[0];
+ EXPECT_EQ(kGAIACookieURL, cookie->Source());
+ EXPECT_EQ(kCookieName, cookie->Name());
+ EXPECT_EQ(expected_gaia_cookie_value, cookie->Value());
+ cookie = &user_cookies[1];
+ EXPECT_EQ(kSAMLIdPCookieURL, cookie->Source());
+ EXPECT_EQ(kCookieName, cookie->Name());
+ EXPECT_EQ(expected_saml_idp_cookie_value, cookie->Value());
+}
+
+void ProfileAuthDataTest::VerifyUserChannelID(
+ const std::string& expected_private_key,
+ const std::string& expected_cert) {
+ net::ChannelIDStore::ChannelIDList user_channel_ids = GetUserChannelIDs();
+ ASSERT_EQ(1u, user_channel_ids.size());
+ net::ChannelIDStore::ChannelID* channel_id = &user_channel_ids.front();
+ EXPECT_EQ(kChannelIDServerIdentifier, channel_id->server_identifier());
+ EXPECT_EQ(expected_private_key, channel_id->private_key());
+ EXPECT_EQ(expected_cert, channel_id->cert());
+}
+
+void ProfileAuthDataTest::PopulateBrowserContext(
+ content::BrowserContext* browser_context,
+ const std::string& proxy_auth_password,
+ const std::string& cookie_value,
+ const std::string& channel_id_private_key,
+ const std::string& channel_id_cert) {
+ GetProxyAuth(browser_context)->Add(
+ GURL(kProxyAuthURL),
+ kProxyAuthRealm,
+ net::HttpAuth::AUTH_SCHEME_BASIC,
+ kProxyAuthChallenge,
+ net::AuthCredentials(base::string16(),
+ base::ASCIIToUTF16(proxy_auth_password)),
+ std::string());
+
+ net::CookieMonster* cookies = GetCookies(browser_context);
+ run_loop_.reset(new base::RunLoop);
+ cookies->SetCookieWithDetailsAsync(
+ GURL(kGAIACookieURL),
+ kCookieName,
+ cookie_value,
+ kGAIACookieDomain,
+ std::string(),
+ base::Time(),
+ true,
+ false,
+ net::COOKIE_PRIORITY_DEFAULT,
+ base::Bind(&ProfileAuthDataTest::QuitLoop, base::Unretained(this)));
+ run_loop_->Run();
+ run_loop_.reset(new base::RunLoop);
+ cookies->SetCookieWithDetailsAsync(
+ GURL(kSAMLIdPCookieURL),
+ kCookieName,
+ cookie_value,
+ kSAMLIdPCookieDomain,
+ std::string(),
+ base::Time(),
+ true,
+ false,
+ net::COOKIE_PRIORITY_DEFAULT,
+ base::Bind(&ProfileAuthDataTest::QuitLoop, base::Unretained(this)));
+ run_loop_->Run();
+
+ GetChannelIDs(browser_context)->SetChannelID(kChannelIDServerIdentifier,
+ base::Time(),
+ base::Time(),
+ channel_id_private_key,
+ channel_id_cert);
+}
+
+net::URLRequestContext* ProfileAuthDataTest::GetRequestContext(
+ content::BrowserContext* browser_context) {
+ return browser_context->GetRequestContext()->GetURLRequestContext();
+}
+
+net::HttpAuthCache* ProfileAuthDataTest::GetProxyAuth(
+ content::BrowserContext* browser_context) {
+ return GetRequestContext(browser_context)->http_transaction_factory()->
+ GetSession()->http_auth_cache();
+}
+
+net::CookieMonster* ProfileAuthDataTest::GetCookies(
+ content::BrowserContext* browser_context) {
+ return GetRequestContext(browser_context)->cookie_store()->GetCookieMonster();
+}
+
+net::ChannelIDStore* ProfileAuthDataTest::GetChannelIDs(
+ content::BrowserContext* browser_context) {
+ return GetRequestContext(browser_context)->channel_id_service()->
+ GetChannelIDStore();
+}
+
+void ProfileAuthDataTest::QuitLoop(bool ignored) {
+ run_loop_->Quit();
+}
+
+void ProfileAuthDataTest::StoreCookieListAndQuitLoop(
+ const net::CookieList& cookie_list) {
+ user_cookie_list_ = cookie_list;
+ run_loop_->Quit();
+}
+
+void ProfileAuthDataTest::StoreChannelIDListAndQuitLoop(
+ const net::ChannelIDStore::ChannelIDList& channel_id_list) {
+ user_channel_id_list_ = channel_id_list;
+ run_loop_->Quit();
+}
+
+// Verifies that when no transfer of auth cookies or channel IDs is requested,
+// only the proxy auth state is transferred.
+TEST_F(ProfileAuthDataTest, DoNotTransfer) {
+ Transfer(false, false);
+
+ VerifyTransferredUserProxyAuthEntry();
+ EXPECT_TRUE(GetUserCookies().empty());
+ EXPECT_TRUE(GetUserChannelIDs().empty());
+}
+
+// Verifies that when the transfer of auth cookies and channel IDs on first
+// login is requested, they do get transferred along with the proxy auth state
+// on first login.
+TEST_F(ProfileAuthDataTest, TransferOnFirstLoginWithNewProfile) {
+ Transfer(true, false);
+
+ VerifyTransferredUserProxyAuthEntry();
+ VerifyUserCookies(kCookieValue1, kCookieValue1);
+ VerifyUserChannelID(kChannelIDPrivateKey1, kChannelIDCert1);
+}
+
+// Verifies that even if the transfer of auth cookies and channel IDs on first
+// login is requested, only the proxy auth state is transferred on subsequent
+// login.
+TEST_F(ProfileAuthDataTest, TransferOnFirstLoginWithExistingProfile) {
+ PopulateUserBrowserContext();
+
+ Transfer(true, false);
+
+ VerifyTransferredUserProxyAuthEntry();
+ VerifyUserCookies(kCookieValue2, kCookieValue2);
+ VerifyUserChannelID(kChannelIDPrivateKey2, kChannelIDCert2);
+}
+
+// Verifies that when the transfer of auth cookies set by a SAML IdP on
+// subsequent login is requested, they do get transferred along with the proxy
+// auth state on subsequent login.
+TEST_F(ProfileAuthDataTest, TransferOnSubsequentLogin) {
+ PopulateUserBrowserContext();
+
+ Transfer(false, true);
+
+ VerifyTransferredUserProxyAuthEntry();
+ VerifyUserCookies(kCookieValue2, kCookieValue1);
+ VerifyUserChannelID(kChannelIDPrivateKey2, kChannelIDCert2);
+}
+
+} // namespace chromeos
diff --git a/chrome/browser/chromeos/login/saml/saml_browsertest.cc b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
index 223184f..1c850ef 100644
--- a/chrome/browser/chromeos/login/saml/saml_browsertest.cc
+++ b/chrome/browser/chromeos/login/saml/saml_browsertest.cc
@@ -2,14 +2,20 @@
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
+#include "base/bind.h"
+#include "base/bind_helpers.h"
+#include "base/callback.h"
#include "base/command_line.h"
#include "base/file_util.h"
#include "base/files/file_path.h"
+#include "base/location.h"
+#include "base/memory/ref_counted.h"
#include "base/memory/scoped_ptr.h"
#include "base/path_service.h"
#include "base/run_loop.h"
#include "base/strings/string16.h"
#include "base/strings/string_util.h"
+#include "base/strings/stringprintf.h"
#include "base/strings/utf_string_conversions.h"
#include "base/values.h"
#include "chrome/browser/chrome_notification_types.h"
@@ -20,16 +26,28 @@
#include "chrome/browser/chromeos/login/ui/webui_login_display.h"
#include "chrome/browser/chromeos/login/users/user_manager.h"
#include "chrome/browser/chromeos/login/wizard_controller.h"
+#include "chrome/browser/chromeos/policy/device_policy_builder.h"
+#include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h"
+#include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h"
+#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/lifetime/application_lifetime.h"
+#include "chrome/browser/profiles/profile.h"
#include "chrome/common/chrome_paths.h"
#include "chrome/common/chrome_switches.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chromeos/chromeos_switches.h"
+#include "chromeos/dbus/dbus_thread_manager.h"
+#include "chromeos/dbus/fake_dbus_thread_manager.h"
+#include "chromeos/dbus/fake_session_manager_client.h"
+#include "chromeos/dbus/session_manager_client.h"
+#include "chromeos/settings/cros_settings_names.h"
#include "components/policy/core/browser/browser_policy_connector.h"
#include "components/policy/core/common/mock_configuration_policy_provider.h"
#include "components/policy/core/common/policy_map.h"
#include "components/policy/core/common/policy_types.h"
#include "components/user_manager/user.h"
+#include "content/public/browser/browser_thread.h"
#include "content/public/browser/web_contents.h"
#include "content/public/test/browser_test_utils.h"
#include "content/public/test/test_utils.h"
@@ -37,16 +55,24 @@
#include "google_apis/gaia/gaia_switches.h"
#include "grit/generated_resources.h"
#include "net/base/url_util.h"
+#include "net/cookies/canonical_cookie.h"
+#include "net/cookies/cookie_monster.h"
+#include "net/cookies/cookie_store.h"
#include "net/dns/mock_host_resolver.h"
#include "net/test/embedded_test_server/embedded_test_server.h"
#include "net/test/embedded_test_server/http_request.h"
#include "net/test/embedded_test_server/http_response.h"
+#include "net/url_request/url_request_context.h"
+#include "net/url_request/url_request_context_getter.h"
#include "policy/policy_constants.h"
+#include "policy/proto/device_management_backend.pb.h"
#include "testing/gmock/include/gmock/gmock.h"
#include "testing/gtest/include/gtest/gtest.h"
#include "ui/base/l10n/l10n_util.h"
#include "url/gurl.h"
+namespace em = enterprise_management;
+
using net::test_server::BasicHttpResponse;
using net::test_server::HttpRequest;
using net::test_server::HttpResponse;
@@ -70,6 +96,10 @@ const char kFirstSAMLUserEmail[] = "bob@example.com";
const char kSecondSAMLUserEmail[] = "alice@example.com";
const char kHTTPSAMLUserEmail[] = "carol@example.com";
const char kNonSAMLUserEmail[] = "dan@example.com";
+const char kDifferentDomainSAMLUserEmail[] = "eve@example.test";
+
+const char kSAMLIdPCookieValue1[] = "value-1";
+const char kSAMLIdPCookieValue2[] = "value-2";
const char kRelayState[] = "RelayState";
@@ -87,6 +117,7 @@ class FakeSamlIdp {
void SetLoginHTMLTemplate(const std::string& template_file);
void SetLoginAuthHTMLTemplate(const std::string& template_file);
void SetRefreshURL(const GURL& refresh_url);
+ void SetCookieValue(const std::string& cookie_value);
scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
@@ -104,6 +135,7 @@ class FakeSamlIdp {
std::string login_auth_html_template_;
GURL gaia_assertion_url_;
GURL refresh_url_;
+ std::string cookie_value_;
DISALLOW_COPY_AND_ASSIGN(FakeSamlIdp);
};
@@ -140,6 +172,10 @@ void FakeSamlIdp::SetRefreshURL(const GURL& refresh_url) {
refresh_url_ = refresh_url;
}
+void FakeSamlIdp::SetCookieValue(const std::string& cookie_value) {
+ cookie_value_ = cookie_value;
+}
+
scoped_ptr<HttpResponse> FakeSamlIdp::HandleRequest(
const HttpRequest& request) {
// The scheme and host of the URL is actually not important but required to
@@ -178,6 +214,9 @@ scoped_ptr<HttpResponse> FakeSamlIdp::HandleRequest(
scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
http_response->AddCustomHeader("Location", redirect_url.spec());
+ http_response->AddCustomHeader(
+ "Set-cookie",
+ base::StringPrintf("saml=%s", cookie_value_.c_str()));
return http_response.PassAs<HttpResponse>();
}
@@ -251,6 +290,7 @@ class SamlTest : public InProcessBrowserTest {
fake_gaia_.RegisterSamlUser(
kHTTPSAMLUserEmail,
embedded_test_server()->base_url().Resolve("/SAML"));
+ fake_gaia_.RegisterSamlUser(kDifferentDomainSAMLUserEmail, saml_idp_url);
fake_gaia_.Initialize();
}
@@ -302,11 +342,12 @@ class SamlTest : public InProcessBrowserTest {
}
void WaitForSigninScreen() {
- WizardController::SkipPostLoginScreensForTesting();
WizardController* wizard_controller =
- chromeos::WizardController::default_controller();
- CHECK(wizard_controller);
- wizard_controller->SkipToLoginForTesting(LoginScreenContext());
+ WizardController::default_controller();
+ if (wizard_controller) {
+ WizardController::SkipPostLoginScreensForTesting();
+ wizard_controller->SkipToLoginForTesting(LoginScreenContext());
+ }
login_screen_load_observer_->Wait();
}
@@ -618,23 +659,59 @@ class SAMLPolicyTest : public SamlTest {
virtual void SetUpOnMainThread() OVERRIDE;
void SetSAMLOfflineSigninTimeLimitPolicy(int limit);
+ void EnableTransferSAMLCookiesPolicy();
+
+ void ShowGAIALoginForm();
+ void LogInWithSAML(const std::string& user_id);
+ void VerifySAMLIdPCookieValue(const std::string& expected_cookie_value);
+
+ void GetCookiesOnIOThread(
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const base::Closure& callback);
+ void StoreCookieList(const base::Closure& callback,
+ const net::CookieList& cookie_list);
protected:
+ policy::DevicePolicyCrosTestHelper test_helper_;
+
+ // FakeDBusThreadManager uses FakeSessionManagerClient.
+ FakeDBusThreadManager* fake_dbus_thread_manager_;
+ FakeSessionManagerClient* fake_session_manager_client_;
+ policy::DevicePolicyBuilder* device_policy_;
+
policy::MockConfigurationPolicyProvider provider_;
+ net::CookieList cookie_list_;
+
private:
DISALLOW_COPY_AND_ASSIGN(SAMLPolicyTest);
};
-SAMLPolicyTest::SAMLPolicyTest() {
+SAMLPolicyTest::SAMLPolicyTest()
+ : fake_dbus_thread_manager_(new FakeDBusThreadManager),
+ fake_session_manager_client_(new FakeSessionManagerClient),
+ device_policy_(test_helper_.device_policy()) {
+ fake_dbus_thread_manager_->SetFakeClients();
+ fake_dbus_thread_manager_->SetSessionManagerClient(
+ scoped_ptr<SessionManagerClient>(fake_session_manager_client_));
}
SAMLPolicyTest::~SAMLPolicyTest() {
}
void SAMLPolicyTest::SetUpInProcessBrowserTestFixture() {
+ DBusThreadManager::SetInstanceForTesting(fake_dbus_thread_manager_);
SamlTest::SetUpInProcessBrowserTestFixture();
+ // Initialize device policy.
+ test_helper_.InstallOwnerKey();
+ test_helper_.MarkAsEnterpriseOwned();
+ device_policy_->SetDefaultSigningKey();
+ device_policy_->Build();
+ fake_session_manager_client_->set_device_policy(device_policy_->GetBlob());
+ fake_session_manager_client_->OnPropertyChangeComplete(true);
+
+ // Initialize user policy.
EXPECT_CALL(provider_, IsInitializationComplete(_))
.WillRepeatedly(Return(true));
policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_);
@@ -648,19 +725,117 @@ void SAMLPolicyTest::SetUpOnMainThread() {
kFirstSAMLUserEmail, user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
UserManager::Get()->SaveUserOAuthStatus(
kNonSAMLUserEmail, user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
+ UserManager::Get()->SaveUserOAuthStatus(
+ kDifferentDomainSAMLUserEmail,
+ user_manager::User::OAUTH2_TOKEN_STATUS_VALID);
}
void SAMLPolicyTest::SetSAMLOfflineSigninTimeLimitPolicy(int limit) {
- policy::PolicyMap policy;
- policy.Set(policy::key::kSAMLOfflineSigninTimeLimit,
- policy::POLICY_LEVEL_MANDATORY,
- policy::POLICY_SCOPE_USER,
- new base::FundamentalValue(limit),
- NULL);
- provider_.UpdateChromePolicy(policy);
+ policy::PolicyMap user_policy;
+ user_policy.Set(policy::key::kSAMLOfflineSigninTimeLimit,
+ policy::POLICY_LEVEL_MANDATORY,
+ policy::POLICY_SCOPE_USER,
+ new base::FundamentalValue(limit),
+ NULL);
+ provider_.UpdateChromePolicy(user_policy);
base::RunLoop().RunUntilIdle();
}
+void SAMLPolicyTest::EnableTransferSAMLCookiesPolicy() {
+ em::ChromeDeviceSettingsProto& proto(device_policy_->payload());
+ proto.mutable_saml_settings()->set_transfer_saml_cookies(true);
+
+ base::RunLoop run_loop;
+ scoped_ptr<CrosSettings::ObserverSubscription> observer =
+ CrosSettings::Get()->AddSettingsObserver(
+ kAccountsPrefTransferSAMLCookies,
+ run_loop.QuitClosure());
+ device_policy_->SetDefaultSigningKey();
+ device_policy_->Build();
+ fake_session_manager_client_->set_device_policy(device_policy_->GetBlob());
+ fake_session_manager_client_->OnPropertyChangeComplete(true);
+ run_loop.Run();
+}
+
+void SAMLPolicyTest::ShowGAIALoginForm() {
+ login_screen_load_observer_->Wait();
+ ASSERT_TRUE(content::ExecuteScript(
+ GetLoginUI()->GetWebContents(),
+ "$('gaia-signin').gaiaAuthHost_.addEventListener('ready', function() {"
+ " window.domAutomationController.setAutomationId(0);"
+ " window.domAutomationController.send('ready');"
+ "});"
+ "$('add-user-button').click();"));
+ content::DOMMessageQueue message_queue;
+ std::string message;
+ ASSERT_TRUE(message_queue.WaitForMessage(&message));
+ EXPECT_EQ("\"ready\"", message);
+}
+
+void SAMLPolicyTest::LogInWithSAML(const std::string& user_id) {
+ fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
+ StartSamlAndWaitForIdpPageLoad(user_id);
+
+ SetMergeSessionParams(user_id);
+ SetSignFormField("Email", "fake_user");
+ SetSignFormField("Password", "fake_password");
+ ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
+
+ OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
+
+ SendConfirmPassword("fake_password");
+ content::WindowedNotificationObserver(
+ chrome::NOTIFICATION_SESSION_STARTED,
+ content::NotificationService::AllSources()).Wait();
+}
+
+void SAMLPolicyTest::VerifySAMLIdPCookieValue(
+ const std::string& expected_cookie_value) {
+ Profile* profile =chromeos::ProfileHelper::Get()->GetProfileByUser(
+ UserManager::Get()->GetActiveUser());
+ ASSERT_TRUE(profile);
+ base::RunLoop run_loop;
+ content::BrowserThread::PostTask(
+ content::BrowserThread::IO,
+ FROM_HERE,
+ base::Bind(&SAMLPolicyTest::GetCookiesOnIOThread,
+ base::Unretained(this),
+ scoped_refptr<net::URLRequestContextGetter>(
+ profile->GetRequestContext()),
+ run_loop.QuitClosure()));
+ run_loop.Run();
+
+ net::CanonicalCookie const* saml_cookie = NULL;
+ for (net::CookieList::const_iterator it = cookie_list_.begin();
+ it != cookie_list_.end(); ++it) {
+ if (it->Name() == "saml") {
+ saml_cookie = &*it;
+ break;
+ }
+ }
+ ASSERT_TRUE(saml_cookie);
+ EXPECT_EQ(expected_cookie_value, saml_cookie->Value());
+}
+
+void SAMLPolicyTest::GetCookiesOnIOThread(
+ const scoped_refptr<net::URLRequestContextGetter>& request_context,
+ const base::Closure& callback) {
+ request_context->GetURLRequestContext()->cookie_store()->
+ GetCookieMonster()->GetAllCookiesAsync(base::Bind(
+ &SAMLPolicyTest::StoreCookieList,
+ base::Unretained(this),
+ callback));
+}
+
+void SAMLPolicyTest::StoreCookieList(
+ const base::Closure& callback,
+ const net::CookieList& cookie_list) {
+ cookie_list_ = cookie_list;
+ content::BrowserThread::PostTask(content::BrowserThread::UI,
+ FROM_HERE,
+ callback);
+}
+
IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_NoSAML) {
// Set the offline login time limit for SAML users to zero.
SetSAMLOfflineSigninTimeLimitPolicy(0);
@@ -688,20 +863,7 @@ IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLNoLimit) {
// Remove the offline login time limit for SAML users.
SetSAMLOfflineSigninTimeLimitPolicy(-1);
- // Log in with SAML.
- fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
- StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
-
- SetSignFormField("Email", "fake_user");
- SetSignFormField("Password", "fake_password");
- ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
-
- OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
-
- SendConfirmPassword("fake_password");
- content::WindowedNotificationObserver(
- chrome::NOTIFICATION_SESSION_STARTED,
- content::NotificationService::AllSources()).Wait();
+ LogInWithSAML(kFirstSAMLUserEmail);
}
// Verifies that when no offline login time limit is set, a user who
@@ -717,20 +879,7 @@ IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLZeroLimit) {
// Set the offline login time limit for SAML users to zero.
SetSAMLOfflineSigninTimeLimitPolicy(0);
- // Log in with SAML.
- fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
- StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail);
-
- SetSignFormField("Email", "fake_user");
- SetSignFormField("Password", "fake_password");
- ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
-
- OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
-
- SendConfirmPassword("fake_password");
- content::WindowedNotificationObserver(
- chrome::NOTIFICATION_SESSION_STARTED,
- content::NotificationService::AllSources()).Wait();
+ LogInWithSAML(kFirstSAMLUserEmail);
}
// Verifies that when the offline login time limit is exceeded for a user who
@@ -742,4 +891,56 @@ IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLZeroLimit) {
" '#pod-row .signin-button-container')).display != 'none'");
}
+IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_PRE_TransferCookiesAffiliated) {
+ fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue1);
+ LogInWithSAML(kFirstSAMLUserEmail);
+ VerifySAMLIdPCookieValue(kSAMLIdPCookieValue1);
+}
+
+// Verifies that when the DeviceTransferSAMLCookies policy is not enabled, SAML
+// IdP cookies are not transferred to a user's profile on subsequent login, even
+// if the user belongs to the domain that the device is enrolled into.
+IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_TransferCookiesAffiliated) {
+ fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2);
+ fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
+ ShowGAIALoginForm();
+
+ LogInWithSAML(kFirstSAMLUserEmail);
+ VerifySAMLIdPCookieValue(kSAMLIdPCookieValue1);
+}
+
+// Verifies that when the DeviceTransferSAMLCookies policy is enabled, SAML IdP
+// cookies are transferred to a user's profile on subsequent login when the user
+// belongs to the domain that the device is enrolled into.
+IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, TransferCookiesAffiliated) {
+ fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2);
+ fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
+ ShowGAIALoginForm();
+
+ EnableTransferSAMLCookiesPolicy();
+
+ LogInWithSAML(kFirstSAMLUserEmail);
+ VerifySAMLIdPCookieValue(kSAMLIdPCookieValue2);
+}
+
+IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_TransferCookiesUnaffiliated) {
+ fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue1);
+ LogInWithSAML(kDifferentDomainSAMLUserEmail);
+ VerifySAMLIdPCookieValue(kSAMLIdPCookieValue1);
+}
+
+// Verifies that even if the DeviceTransferSAMLCookies policy is enabled, SAML
+// IdP are not transferred to a user's profile on subsequent login if the user
+// does not belong to the domain that the device is enrolled into.
+IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, TransferCookiesUnaffiliated) {
+ fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2);
+ fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
+ ShowGAIALoginForm();
+
+ EnableTransferSAMLCookiesPolicy();
+
+ LogInWithSAML(kDifferentDomainSAMLUserEmail);
+ VerifySAMLIdPCookieValue(kSAMLIdPCookieValue1);
+}
+
} // namespace chromeos
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.cc b/chrome/browser/chromeos/login/session/user_session_manager.cc
index e4083e3..cd4021d 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.cc
+++ b/chrome/browser/chromeos/login/session/user_session_manager.cc
@@ -20,6 +20,7 @@
#include "base/threading/worker_pool.h"
#include "chrome/browser/app_mode/app_mode_utils.h"
#include "chrome/browser/browser_process.h"
+#include "chrome/browser/browser_process_platform_part_chromeos.h"
#include "chrome/browser/chrome_notification_types.h"
#include "chrome/browser/chromeos/base/locale_util.h"
#include "chrome/browser/chromeos/boot_times_loader.h"
@@ -33,7 +34,9 @@
#include "chrome/browser/chromeos/login/users/supervised_user_manager.h"
#include "chrome/browser/chromeos/login/users/user_manager.h"
#include "chrome/browser/chromeos/ownership/owner_settings_service_factory.h"
+#include "chrome/browser/chromeos/policy/browser_policy_connector_chromeos.h"
#include "chrome/browser/chromeos/profiles/profile_helper.h"
+#include "chrome/browser/chromeos/settings/cros_settings.h"
#include "chrome/browser/first_run/first_run.h"
#include "chrome/browser/google/google_brand_chromeos.h"
#include "chrome/browser/lifetime/application_lifetime.h"
@@ -54,6 +57,8 @@
#include "chromeos/ime/input_method_manager.h"
#include "chromeos/network/portal_detector/network_portal_detector.h"
#include "chromeos/network/portal_detector/network_portal_detector_strategy.h"
+#include "chromeos/settings/cros_settings_names.h"
+#include "components/policy/core/common/cloud/cloud_policy_constants.h"
#include "components/session_manager/core/session_manager.h"
#include "components/signin/core/browser/signin_manager_base.h"
#include "components/user_manager/user.h"
@@ -587,7 +592,7 @@ void UserSessionManager::OnProfileCreated(const std::string& user_id,
// Profile is created, extensions and promo resources are initialized.
// At this point all other Chrome OS services will be notified that it is
// safe to use this profile.
- UserProfileInitialized(profile, is_incognito_profile);
+ UserProfileInitialized(profile, is_incognito_profile, user_id);
break;
case Profile::CREATE_STATUS_LOCAL_FAIL:
case Profile::CREATE_STATUS_REMOTE_FAIL:
@@ -621,7 +626,8 @@ void UserSessionManager::InitProfilePreferences(Profile* profile,
}
void UserSessionManager::UserProfileInitialized(Profile* profile,
- bool is_incognito_profile) {
+ bool is_incognito_profile,
+ const std::string& user_id) {
if (is_incognito_profile) {
profile->OnLogin();
// Send the notification before creating the browser so additional objects
@@ -641,17 +647,35 @@ void UserSessionManager::UserProfileInitialized(Profile* profile,
btl->AddLoginTimeMarker("UserProfileGotten", false);
if (user_context_.IsUsingOAuth()) {
+ // Retrieve the policy that indicates whether to continue copying
+ // authentication cookies set by a SAML IdP on subsequent logins after the
+ // first.
+ bool transfer_saml_auth_cookies_on_subsequent_login = false;
+ if (has_auth_cookies_ &&
+ g_browser_process->platform_part()->
+ browser_policy_connector_chromeos()->GetUserAffiliation(user_id) ==
+ policy::USER_AFFILIATION_MANAGED) {
+ CrosSettings::Get()->GetBoolean(
+ kAccountsPrefTransferSAMLCookies,
+ &transfer_saml_auth_cookies_on_subsequent_login);
+ }
+
// Transfers authentication-related data from the profile that was used for
// authentication to the user's profile. The proxy authentication state is
// transferred unconditionally. If the user authenticated via an auth
- // extension, authentication cookies and server bound certificates will be
- // transferred as well, if the user's cookie jar is empty. If the cookie jar
- // is not empty, the authentication states in the login profile and the
- // user's profile must be merged using /MergeSession instead.
+ // extension, authentication cookies and channel IDs will be transferred as
+ // well when the user's cookie jar is empty. If the cookie jar is not empty,
+ // the authentication states in the login profile and the user's profile
+ // must be merged using /MergeSession instead. Authentication cookies set by
+ // a SAML IdP will also be transferred when the user's cookie jar is not
+ // empty if |transfer_saml_auth_cookies_on_subsequent_login| is true.
+ const bool transfer_auth_cookies_and_channel_ids_on_first_login =
+ has_auth_cookies_;
ProfileAuthData::Transfer(
authenticator_->authentication_profile(),
profile,
- has_auth_cookies_, // transfer_auth_cookies_and_channel_ids
+ transfer_auth_cookies_and_channel_ids_on_first_login,
+ transfer_saml_auth_cookies_on_subsequent_login,
base::Bind(&UserSessionManager::CompleteProfileCreateAfterAuthTransfer,
AsWeakPtr(),
profile));
diff --git a/chrome/browser/chromeos/login/session/user_session_manager.h b/chrome/browser/chromeos/login/session/user_session_manager.h
index 9344cb8..9ff019a 100644
--- a/chrome/browser/chromeos/login/session/user_session_manager.h
+++ b/chrome/browser/chromeos/login/session/user_session_manager.h
@@ -178,7 +178,9 @@ class UserSessionManager
// Callback for Profile::CREATE_STATUS_INITIALIZED profile state.
// Profile is created, extensions and promo resources are initialized.
- void UserProfileInitialized(Profile* profile, bool is_incognito_profile);
+ void UserProfileInitialized(Profile* profile,
+ bool is_incognito_profile,
+ const std::string& user_id);
// Callback to resume profile creation after transferring auth data from
// the authentication profile.
diff --git a/chrome/browser/chromeos/login/test/https_forwarder.py b/chrome/browser/chromeos/login/test/https_forwarder.py
index 5163220..6e7157b 100644
--- a/chrome/browser/chromeos/login/test/https_forwarder.py
+++ b/chrome/browser/chromeos/login/test/https_forwarder.py
@@ -64,7 +64,22 @@ class RequestForwarder(BaseHTTPServer.BaseHTTPRequestHandler):
self.send_response(forward.getcode())
for key, value in dict(forward.info()).iteritems():
- self.send_header(key, value)
+ # RFC 6265 states in section 3:
+ #
+ # Origin servers SHOULD NOT fold multiple Set-Cookie header fields into
+ # a single header field.
+ #
+ # Python 2 does not obey this requirement and folds multiple Set-Cookie
+ # header fields into one. The following code undoes this folding by
+ # splitting the Set-Cookie header field at each comma. Note that this is a
+ # hack because the code does not (and cannot reliably) distinguish between
+ # commas inserted by Python while folding multiple headers and commas that
+ # were part of the original Set-Cookie headers.
+ if key == 'set-cookie':
+ for cookie in value.split(','):
+ self.send_header(key, cookie)
+ else:
+ self.send_header(key, value)
self.end_headers()
self.wfile.write(forward.read())
diff --git a/chrome/browser/chromeos/policy/device_policy_decoder_chromeos.cc b/chrome/browser/chromeos/policy/device_policy_decoder_chromeos.cc
index 7856cff..a2936a2 100644
--- a/chrome/browser/chromeos/policy/device_policy_decoder_chromeos.cc
+++ b/chrome/browser/chromeos/policy/device_policy_decoder_chromeos.cc
@@ -259,6 +259,18 @@ void DecodeLoginPolicies(const em::ChromeDeviceSettingsProto& policy,
NULL);
}
}
+
+ if (policy.has_saml_settings()) {
+ const em::SAMLSettingsProto& container(policy.saml_settings());
+ if (container.has_transfer_saml_cookies()) {
+ policies->Set(key::kDeviceTransferSAMLCookies,
+ POLICY_LEVEL_MANDATORY,
+ POLICY_SCOPE_MACHINE,
+ new base::FundamentalValue(
+ container.transfer_saml_cookies()),
+ NULL);
+ }
+ }
}
void DecodeKioskPolicies(const em::ChromeDeviceSettingsProto& policy,
diff --git a/chrome/browser/chromeos/policy/proto/chrome_device_policy.proto b/chrome/browser/chromeos/policy/proto/chrome_device_policy.proto
index 180b57b..818c469 100644
--- a/chrome/browser/chromeos/policy/proto/chrome_device_policy.proto
+++ b/chrome/browser/chromeos/policy/proto/chrome_device_policy.proto
@@ -573,6 +573,14 @@ message SystemSettingsProto {
optional bool block_devmode = 1;
}
+// Settings that control login for SAML users.
+message SAMLSettingsProto {
+ // Whether cookies set by a SAML IdP should be transferred to users' profiles
+ // every time a user authenticates via SAML during login. If false, cookies
+ // are transferred during each user's first login only.
+ optional bool transfer_saml_cookies = 1;
+}
+
message ChromeDeviceSettingsProto {
optional DevicePolicyRefreshRateProto device_policy_refresh_rate = 1;
optional UserWhitelistProto user_whitelist = 2;
@@ -606,4 +614,5 @@ message ChromeDeviceSettingsProto {
optional SystemUse24HourClockProto use_24hour_clock = 30;
optional AutoCleanupSettigsProto auto_clean_up_settings = 31;
optional SystemSettingsProto system_settings = 32;
+ optional SAMLSettingsProto saml_settings = 33;
}
diff --git a/chrome/browser/chromeos/settings/device_settings_provider.cc b/chrome/browser/chromeos/settings/device_settings_provider.cc
index 50d78d4..1e94ace7 100644
--- a/chrome/browser/chromeos/settings/device_settings_provider.cc
+++ b/chrome/browser/chromeos/settings/device_settings_provider.cc
@@ -47,6 +47,7 @@ const char* kKnownSettings[] = {
kAccountsPrefEphemeralUsersEnabled,
kAccountsPrefShowUserNamesOnSignIn,
kAccountsPrefSupervisedUsersEnabled,
+ kAccountsPrefTransferSAMLCookies,
kAccountsPrefUsers,
kAllowRedeemChromeOsRegistrationOffers,
kAllowedConnectionTypesForUpdate,
@@ -397,6 +398,7 @@ void DeviceSettingsProvider::SetInPolicy() {
} else {
// The remaining settings don't support Set(), since they are not
// intended to be customizable by the user:
+ // kAccountsPrefTransferSAMLCookies
// kAppPack
// kDeviceAttestationEnabled
// kDeviceOwner
@@ -448,6 +450,7 @@ void DeviceSettingsProvider::DecodeLoginPolicies(
// kAccountsPrefEphemeralUsersEnabled has a default value of false.
// kAccountsPrefSupervisedUsersEnabled has a default value of false
// for enterprise devices and true for consumer devices.
+ // kAccountsPrefTransferSAMLCookies has a default value of false.
if (policy.has_allow_new_users() &&
policy.allow_new_users().has_allow_new_users()) {
if (policy.allow_new_users().allow_new_users()) {
@@ -574,6 +577,12 @@ void DeviceSettingsProvider::DecodeLoginPolicies(
}
new_values_cache->SetValue(kStartUpFlags, list);
}
+
+ if (policy.has_saml_settings()) {
+ new_values_cache->SetBoolean(
+ kAccountsPrefTransferSAMLCookies,
+ policy.saml_settings().transfer_saml_cookies());
+ }
}
void DeviceSettingsProvider::DecodeKioskPolicies(
diff --git a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
index 332eef4..d2d7103 100644
--- a/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
+++ b/chrome/browser/ui/webui/chromeos/login/gaia_screen_handler.cc
@@ -407,7 +407,7 @@ void GaiaScreenHandler::OnCookiesCleared(
void GaiaScreenHandler::ShowSigninScreenForCreds(const std::string& username,
const std::string& password) {
VLOG(2) << "ShowSigninScreenForCreds for user " << username
- << ", frame_state=" << FrameState();
+ << ", frame_state=" << frame_state();
test_user_ = username;
test_pass_ = password;
@@ -416,9 +416,9 @@ void GaiaScreenHandler::ShowSigninScreenForCreds(const std::string& username,
// Submit login form for test if gaia is ready. If gaia is loading, login
// will be attempted in HandleLoginWebuiReady after gaia is ready. Otherwise,
// reload gaia then follow the loading case.
- if (FrameState() == GaiaScreenHandler::FRAME_STATE_LOADED) {
+ if (frame_state() == GaiaScreenHandler::FRAME_STATE_LOADED) {
SubmitLoginFormForTest();
- } else if (FrameState() != GaiaScreenHandler::FRAME_STATE_LOADING) {
+ } else if (frame_state() != GaiaScreenHandler::FRAME_STATE_LOADING) {
DCHECK(signin_screen_handler_);
signin_screen_handler_->OnShowAddUser();
}
diff --git a/chrome/chrome_tests_unit.gypi b/chrome/chrome_tests_unit.gypi
index 5a548bb..2925608 100644
--- a/chrome/chrome_tests_unit.gypi
+++ b/chrome/chrome_tests_unit.gypi
@@ -774,6 +774,7 @@
'browser/chromeos/login/auth/parallel_authenticator_unittest.cc',
'browser/chromeos/login/existing_user_controller_auto_login_unittest.cc',
'browser/chromeos/login/hwid_checker_unittest.cc',
+ 'browser/chromeos/login/profile_auth_data_unittest.cc',
'browser/chromeos/login/saml/saml_offline_signin_limiter_unittest.cc',
'browser/chromeos/login/screens/screen_context_unittest.cc',
'browser/chromeos/login/signin/merge_session_load_page_unittest.cc',
diff --git a/chrome/test/data/policy/policy_test_cases.json b/chrome/test/data/policy/policy_test_cases.json
index 16b09d9..5c41abf 100644
--- a/chrome/test/data/policy/policy_test_cases.json
+++ b/chrome/test/data/policy/policy_test_cases.json
@@ -2314,6 +2314,9 @@
"AutoCleanUpStrategy": {
},
+ "DeviceTransferSAMLCookies": {
+ },
+
"----- Chrome Frame policies -------------------------------------------": {},
"ChromeFrameRendererSettings": {
diff --git a/chromeos/settings/cros_settings_names.cc b/chromeos/settings/cros_settings_names.cc
index f70a202..a885596 100644
--- a/chromeos/settings/cros_settings_names.cc
+++ b/chromeos/settings/cros_settings_names.cc
@@ -34,6 +34,8 @@ const char kAccountsPrefDeviceLocalAccountPromptForNetworkWhenOffline[] =
"cros.accounts.deviceLocalAccountPromptForNetworkWhenOffline";
const char kAccountsPrefSupervisedUsersEnabled[] =
"cros.accounts.supervisedUsersEnabled";
+const char kAccountsPrefTransferSAMLCookies[] =
+ "cros.accounts.transferSAMLCookies";
// All cros.signed.* settings are stored in SignedSettings.
const char kSignedDataRoamingEnabled[] = "cros.signed.data_roaming_enabled";
diff --git a/chromeos/settings/cros_settings_names.h b/chromeos/settings/cros_settings_names.h
index 1e1097e..4c60f1d 100644
--- a/chromeos/settings/cros_settings_names.h
+++ b/chromeos/settings/cros_settings_names.h
@@ -30,6 +30,7 @@ CHROMEOS_EXPORT extern const char
CHROMEOS_EXPORT extern const char
kAccountsPrefDeviceLocalAccountPromptForNetworkWhenOffline[];
CHROMEOS_EXPORT extern const char kAccountsPrefSupervisedUsersEnabled[];
+CHROMEOS_EXPORT extern const char kAccountsPrefTransferSAMLCookies[];
CHROMEOS_EXPORT extern const char kSignedDataRoamingEnabled[];
diff --git a/components/policy/resources/policy_templates.json b/components/policy/resources/policy_templates.json
index 47f26f6..bffc694 100644
--- a/components/policy/resources/policy_templates.json
+++ b/components/policy/resources/policy_templates.json
@@ -120,7 +120,7 @@
# persistent IDs for all fields (but not for groups!) are needed. These are
# specified by the 'id' keys of each policy. NEVER CHANGE EXISTING IDs,
# because doing so would break the deployed wire format!
-# For your editing convenience: highest ID currently used: 270
+# For your editing convenience: highest ID currently used: 271
#
# Placeholders:
# The following placeholder strings are automatically substituted:
@@ -6528,6 +6528,28 @@
While the policy itself is supported on the above platforms, the feature it is enabling may be available on fewer platforms. Not all deprecated Web Platform features can be re-enabled. Only the ones explicitly listed below can be for a limited period of time, which is different per feature. The general format of the string tag will be [DeprecatedFeatureName]_EffectiveUntil[yyyymmdd]. As reference, you can find the intent behind the Web Platform feature changes at http://bit.ly/blinkintents.
''',
},
+ {
+ 'name': 'DeviceTransferSAMLCookies',
+ 'type': 'main',
+ 'schema': { 'type': 'boolean' },
+ 'supported_on': ['chrome_os:38-'],
+ 'device_only': True,
+ 'features': {
+ 'dynamic_refresh': True,
+ },
+ 'example_value': True,
+ 'id': 271,
+ 'caption': '''Transfer SAML IdP cookies during login''',
+ 'desc': '''Specifies whether authentication cookies set by a SAML IdP during login should be transferred to the user's profile.
+
+ When a user authenticates via a SAML IdP during login, cookies set by the IdP are written to a temporary profile at first. These cookies can be transferred to the user's profile to carry forward the authentication state.
+
+ When this policy is set to true, cookies set by the IdP are transferred to the user's profile every time he/she authenticates against the SAML IdP during login.
+
+ When this policy is set to false or unset, cookies set by the IdP are transferred to the user's profile during his/her first login on a device only.
+
+ This policy affects users whose domain matches the device's enrollment domain only. For all other users, cookies set by the IdP are transferred to the user's profile during his/her first login on the device only.''',
+ },
],
'messages': {
# Messages that are not associated to any policies.
diff --git a/google_apis/gaia/fake_gaia.cc b/google_apis/gaia/fake_gaia.cc
index cdc1237..a1c04cb 100644
--- a/google_apis/gaia/fake_gaia.cc
+++ b/google_apis/gaia/fake_gaia.cc
@@ -102,10 +102,10 @@ void SetCookies(BasicHttpResponse* http_response,
const std::string& lsid_cookie) {
http_response->AddCustomHeader(
"Set-Cookie",
- base::StringPrintf("SID=%s; Path=/; HttpOnly;", sid_cookie.c_str()));
+ base::StringPrintf("SID=%s; Path=/; HttpOnly", sid_cookie.c_str()));
http_response->AddCustomHeader(
"Set-Cookie",
- base::StringPrintf("LSID=%s; Path=/; HttpOnly;", lsid_cookie.c_str()));
+ base::StringPrintf("LSID=%s; Path=/; HttpOnly", lsid_cookie.c_str()));
}
} // namespace
diff --git a/tools/metrics/histograms/histograms.xml b/tools/metrics/histograms/histograms.xml
index ed36ee4..1c598c2 100644
--- a/tools/metrics/histograms/histograms.xml
+++ b/tools/metrics/histograms/histograms.xml
@@ -38224,6 +38224,7 @@ Therefore, the affected-histogram name has to have at least one dot in it.
<int value="268" label="Register protocol handlers"/>
<int value="269" label="Enable virtual keyboard"/>
<int value="270" label="Enable deprecated web platform features"/>
+ <int value="271" label="Transfer SAML IdP cookies during login"/>
</enum>
<enum name="EnterprisePolicyInvalidations" type="int">