diff options
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"> |