| // 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 <string> |
| #include <vector> |
| |
| #include "base/bind.h" |
| #include "base/command_line.h" |
| #include "base/macros.h" |
| #include "base/run_loop.h" |
| #include "base/strings/string_util.h" |
| #include "chrome/browser/chrome_notification_types.h" |
| #include "chrome/browser/chromeos/login/screens/gaia_view.h" |
| #include "chrome/browser/chromeos/login/test/oobe_base_test.h" |
| #include "chrome/browser/chromeos/login/ui/login_display_host.h" |
| #include "chrome/browser/chromeos/login/wizard_controller.h" |
| #include "chrome/browser/chromeos/profiles/profile_helper.h" |
| #include "chrome/browser/chromeos/settings/install_attributes.h" |
| #include "chrome/browser/ui/webui/chromeos/login/signin_screen_handler.h" |
| #include "chrome/common/chrome_switches.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chromeos/chromeos_switches.h" |
| #include "components/policy/core/common/cloud/device_management_service.h" |
| #include "components/policy/core/common/policy_switches.h" |
| #include "components/policy/proto/device_management_backend.pb.h" |
| #include "components/user_manager/known_user.h" |
| #include "components/user_manager/user_manager.h" |
| #include "content/public/browser/notification_observer.h" |
| #include "content/public/browser/notification_registrar.h" |
| #include "content/public/browser/notification_service.h" |
| #include "content/public/test/test_utils.h" |
| #include "google_apis/gaia/gaia_switches.h" |
| #include "google_apis/gaia/gaia_urls.h" |
| #include "net/http/http_status_code.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 "testing/gtest/include/gtest/gtest.h" |
| |
| namespace chromeos { |
| |
| namespace { |
| |
| namespace em = enterprise_management; |
| |
| const char kDomain[] = "domain.com"; |
| const char kUsername[] = "user@domain.com"; |
| const char kUsernameOtherDomain[] = "user@other.com"; |
| |
| const char kOAuthCodeCookie[] = "oauth_code=1234; Secure; HttpOnly"; |
| |
| const char kOAuth2TokenPairData[] = |
| "{" |
| " \"refresh_token\": \"1234\"," |
| " \"access_token\": \"5678\"," |
| " \"expires_in\": 3600" |
| "}"; |
| |
| const char kOAuth2AccessTokenData[] = |
| "{" |
| " \"access_token\": \"5678\"," |
| " \"expires_in\": 3600" |
| "}"; |
| |
| const char kDMRegisterRequest[] = "/device_management?request=register"; |
| const char kDMPolicyRequest[] = "/device_management?request=policy"; |
| |
| void CopyLockResult(base::RunLoop* loop, |
| InstallAttributes::LockResult* out, |
| InstallAttributes::LockResult result) { |
| *out = result; |
| loop->Quit(); |
| } |
| |
| } // namespace |
| |
| struct BlockingLoginTestParam { |
| const int steps; |
| const char* username; |
| const bool enroll_device; |
| }; |
| |
| // TODO(poromov): This test is completely broken - it originally was built |
| // when we made an entirely different set of network calls on startup. As a |
| // result it generates random failures in startup network requests, then waits |
| // to see if the profile finishes loading which is not at all what it is |
| // intended to test. We need to fix this test or remove it (crbug.com/580537). |
| class BlockingLoginTest |
| : public OobeBaseTest, |
| public content::NotificationObserver, |
| public testing::WithParamInterface<BlockingLoginTestParam> { |
| public: |
| BlockingLoginTest() : profile_added_(NULL) { |
| } |
| |
| void SetUpCommandLine(base::CommandLine* command_line) override { |
| OobeBaseTest::SetUpCommandLine(command_line); |
| |
| command_line->AppendSwitchASCII( |
| policy::switches::kDeviceManagementUrl, |
| embedded_test_server()->GetURL("/device_management").spec()); |
| } |
| |
| void SetUpOnMainThread() override { |
| registrar_.Add(this, |
| chrome::NOTIFICATION_PROFILE_ADDED, |
| content::NotificationService::AllSources()); |
| |
| OobeBaseTest::SetUpOnMainThread(); |
| } |
| |
| void TearDownOnMainThread() override { |
| RunUntilIdle(); |
| EXPECT_TRUE(responses_.empty()); |
| OobeBaseTest::TearDownOnMainThread(); |
| } |
| |
| void Observe(int type, |
| const content::NotificationSource& source, |
| const content::NotificationDetails& details) override { |
| ASSERT_EQ(chrome::NOTIFICATION_PROFILE_ADDED, type); |
| if (chromeos::ProfileHelper::IsLockScreenAppProfile( |
| content::Source<Profile>(source).ptr())) { |
| return; |
| } |
| ASSERT_FALSE(profile_added_); |
| profile_added_ = content::Source<Profile>(source).ptr(); |
| } |
| |
| void RunUntilIdle() { |
| base::RunLoop().RunUntilIdle(); |
| } |
| |
| void EnrollDevice(const std::string& domain) { |
| base::RunLoop loop; |
| InstallAttributes::LockResult result; |
| InstallAttributes::Get()->LockDevice( |
| policy::DEVICE_MODE_ENTERPRISE, domain, std::string(), "100200300", |
| base::Bind(&CopyLockResult, &loop, &result)); |
| loop.Run(); |
| EXPECT_EQ(InstallAttributes::LOCK_SUCCESS, result); |
| RunUntilIdle(); |
| } |
| |
| void Login(const std::string& username) { |
| content::WindowedNotificationObserver session_started_observer( |
| chrome::NOTIFICATION_SESSION_STARTED, |
| content::NotificationService::AllSources()); |
| |
| LoginDisplayHost::default_host() |
| ->GetOobeUI() |
| ->GetGaiaScreenView() |
| ->ShowSigninScreenForTest(username, "password", "[]"); |
| |
| // Wait for the session to start after submitting the credentials. This |
| // will wait until all the background requests are done. |
| session_started_observer.Wait(); |
| } |
| |
| // Handles an HTTP request sent to the test server. This handler either |
| // uses a canned response in |responses_| if the request path matches one |
| // of the URLs that we mock, otherwise this handler delegates to |fake_gaia_|. |
| std::unique_ptr<net::test_server::HttpResponse> HandleRequest( |
| const net::test_server::HttpRequest& request) { |
| std::unique_ptr<net::test_server::HttpResponse> response; |
| |
| GaiaUrls* gaia = GaiaUrls::GetInstance(); |
| if (request.relative_url == |
| gaia->deprecated_client_login_to_oauth2_url().path() || |
| request.relative_url == gaia->oauth2_token_url().path() || |
| base::StartsWith(request.relative_url, kDMRegisterRequest, |
| base::CompareCase::SENSITIVE) || |
| base::StartsWith(request.relative_url, kDMPolicyRequest, |
| base::CompareCase::SENSITIVE)) { |
| if (!responses_.empty()) { |
| response = std::move(responses_.back()); |
| responses_.pop_back(); |
| } |
| } |
| |
| return response; |
| } |
| |
| // Creates a new canned response that will respond with the given HTTP |
| // status |code|. That response is appended to |responses_| and will be the |
| // next response used. |
| // Returns a reference to that response, so that it can be further customized. |
| net::test_server::BasicHttpResponse& PushResponse(net::HttpStatusCode code) { |
| auto response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| net::test_server::BasicHttpResponse* response_ptr = response.get(); |
| response->set_code(code); |
| responses_.push_back(std::move(response)); |
| return *response_ptr; |
| } |
| |
| // Returns the body of the register response from the policy server. |
| std::string GetRegisterResponse() { |
| em::DeviceManagementResponse response; |
| em::DeviceRegisterResponse* register_response = |
| response.mutable_register_response(); |
| register_response->set_device_management_token("1234"); |
| register_response->set_enrollment_type( |
| em::DeviceRegisterResponse::ENTERPRISE); |
| std::string data; |
| EXPECT_TRUE(response.SerializeToString(&data)); |
| return data; |
| } |
| |
| // Returns the body of the fetch response from the policy server. |
| std::string GetPolicyResponse() { |
| em::DeviceManagementResponse response; |
| response.mutable_policy_response()->add_response(); |
| std::string data; |
| EXPECT_TRUE(response.SerializeToString(&data)); |
| return data; |
| } |
| |
| protected: |
| void RegisterAdditionalRequestHandlers() override { |
| embedded_test_server()->RegisterRequestHandler( |
| base::Bind(&BlockingLoginTest::HandleRequest, base::Unretained(this))); |
| } |
| |
| Profile* profile_added_; |
| |
| private: |
| std::vector<std::unique_ptr<net::test_server::HttpResponse>> responses_; |
| content::NotificationRegistrar registrar_; |
| |
| DISALLOW_COPY_AND_ASSIGN(BlockingLoginTest); |
| }; |
| |
| IN_PROC_BROWSER_TEST_P(BlockingLoginTest, LoginBlocksForUser) { |
| // Verify that there isn't a logged in user when the test starts. |
| user_manager::UserManager* user_manager = user_manager::UserManager::Get(); |
| EXPECT_FALSE(user_manager->IsUserLoggedIn()); |
| EXPECT_FALSE(InstallAttributes::Get()->IsEnterpriseManaged()); |
| EXPECT_FALSE(profile_added_); |
| |
| // Enroll the device, if enrollment is enabled for this test instance. |
| if (GetParam().enroll_device) { |
| EnrollDevice(kDomain); |
| |
| EXPECT_FALSE(user_manager->IsUserLoggedIn()); |
| EXPECT_TRUE(InstallAttributes::Get()->IsEnterpriseManaged()); |
| EXPECT_EQ(kDomain, InstallAttributes::Get()->GetDomain()); |
| EXPECT_FALSE(profile_added_); |
| RunUntilIdle(); |
| EXPECT_FALSE( |
| user_manager->IsKnownUser(AccountId::FromUserEmail(kUsername))); |
| } |
| |
| // Skip the OOBE, go to the sign-in screen, and wait for the login screen to |
| // become visible. |
| WaitForSigninScreen(); |
| EXPECT_FALSE(profile_added_); |
| |
| // Prepare the fake HTTP responses. |
| if (GetParam().steps < 5) { |
| // If this instance is not going to complete the entire flow successfully |
| // then the last step will fail. |
| |
| // This response body is important to make the gaia fetcher skip its delayed |
| // retry behavior, which makes testing harder. If this is sent to the policy |
| // fetchers then it will make them fail too. |
| PushResponse(net::HTTP_UNAUTHORIZED).set_content("Error=AccountDeleted"); |
| } |
| |
| // Push a response for each step that is going to succeed, in reverse order. |
| switch (GetParam().steps) { |
| default: |
| ADD_FAILURE() << "Invalid step number: " << GetParam().steps; |
| return; |
| |
| case 5: |
| PushResponse(net::HTTP_OK).set_content(GetPolicyResponse()); |
| FALLTHROUGH; |
| |
| case 4: |
| PushResponse(net::HTTP_OK).set_content(GetRegisterResponse()); |
| FALLTHROUGH; |
| |
| case 3: |
| PushResponse(net::HTTP_OK).set_content(kOAuth2AccessTokenData); |
| FALLTHROUGH; |
| |
| case 2: |
| PushResponse(net::HTTP_OK).set_content(kOAuth2TokenPairData); |
| FALLTHROUGH; |
| |
| case 1: |
| PushResponse(net::HTTP_OK) |
| .AddCustomHeader("Set-Cookie", kOAuthCodeCookie); |
| break; |
| |
| case 0: |
| break; |
| } |
| |
| // Login now. This verifies that logging in with the canned responses (which |
| // may include failures) won't be blocked due to the potential failures. |
| EXPECT_FALSE(profile_added_); |
| Login(GetParam().username); |
| EXPECT_TRUE(profile_added_); |
| ASSERT_TRUE(user_manager->IsUserLoggedIn()); |
| EXPECT_TRUE(user_manager->IsCurrentUserNew()); |
| } |
| |
| const BlockingLoginTestParam kBlockinLoginTestCases[] = { |
| {0, kUsername, true}, |
| {1, kUsername, true}, |
| {2, kUsername, true}, |
| {3, kUsername, true}, |
| {4, kUsername, true}, |
| {5, kUsername, true}, |
| {0, kUsername, false}, |
| {1, kUsername, false}, |
| {2, kUsername, false}, |
| {3, kUsername, false}, |
| {4, kUsername, false}, |
| {5, kUsername, false}, |
| {0, kUsernameOtherDomain, true}, |
| {1, kUsernameOtherDomain, true}, |
| {2, kUsernameOtherDomain, true}, |
| {3, kUsernameOtherDomain, true}, |
| {4, kUsernameOtherDomain, true}, |
| {5, kUsernameOtherDomain, true}, |
| }; |
| |
| // TODO(poromov): Disabled because it has become flaky due to incorrect mock |
| // network requests - re-enable this when https://crbug.com/580537 is fixed. |
| INSTANTIATE_TEST_CASE_P(DISABLED_BlockingLoginTestInstance, |
| BlockingLoginTest, |
| testing::ValuesIn(kBlockinLoginTestCases)); |
| |
| } // namespace chromeos |