1 // Copyright 2014 The Chromium Authors. All rights reserved. 2 // Use of this source code is governed by a BSD-style license that can be 3 // found in the LICENSE file. 4 5 #include "base/bind.h" 6 #include "base/bind_helpers.h" 7 #include "base/callback.h" 8 #include "base/command_line.h" 9 #include "base/files/file_path.h" 10 #include "base/files/file_util.h" 11 #include "base/location.h" 12 #include "base/memory/ref_counted.h" 13 #include "base/memory/scoped_ptr.h" 14 #include "base/path_service.h" 15 #include "base/run_loop.h" 16 #include "base/strings/string16.h" 17 #include "base/strings/string_util.h" 18 #include "base/strings/stringprintf.h" 19 #include "base/strings/utf_string_conversions.h" 20 #include "base/values.h" 21 #include "chrome/browser/chrome_notification_types.h" 22 #include "chrome/browser/chromeos/login/existing_user_controller.h" 23 #include "chrome/browser/chromeos/login/test/https_forwarder.h" 24 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h" 25 #include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" 26 #include "chrome/browser/chromeos/login/ui/webui_login_display.h" 27 #include "chrome/browser/chromeos/login/wizard_controller.h" 28 #include "chrome/browser/chromeos/policy/device_policy_builder.h" 29 #include "chrome/browser/chromeos/policy/device_policy_cros_browser_test.h" 30 #include "chrome/browser/chromeos/policy/proto/chrome_device_policy.pb.h" 31 #include "chrome/browser/chromeos/profiles/profile_helper.h" 32 #include "chrome/browser/chromeos/settings/cros_settings.h" 33 #include "chrome/browser/lifetime/application_lifetime.h" 34 #include "chrome/browser/profiles/profile.h" 35 #include "chrome/browser/ui/webui/signin/inline_login_ui.h" 36 #include "chrome/common/chrome_paths.h" 37 #include "chrome/common/chrome_switches.h" 38 #include "chrome/grit/generated_resources.h" 39 #include "chrome/test/base/in_process_browser_test.h" 40 #include "chromeos/chromeos_switches.h" 41 #include "chromeos/dbus/dbus_thread_manager.h" 42 #include "chromeos/dbus/fake_session_manager_client.h" 43 #include "chromeos/dbus/session_manager_client.h" 44 #include "chromeos/settings/cros_settings_names.h" 45 #include "components/policy/core/browser/browser_policy_connector.h" 46 #include "components/policy/core/common/mock_configuration_policy_provider.h" 47 #include "components/policy/core/common/policy_map.h" 48 #include "components/policy/core/common/policy_types.h" 49 #include "components/user_manager/user.h" 50 #include "components/user_manager/user_manager.h" 51 #include "content/public/browser/browser_thread.h" 52 #include "content/public/browser/web_contents.h" 53 #include "content/public/test/browser_test_utils.h" 54 #include "content/public/test/test_utils.h" 55 #include "google_apis/gaia/fake_gaia.h" 56 #include "google_apis/gaia/gaia_switches.h" 57 #include "net/base/url_util.h" 58 #include "net/cookies/canonical_cookie.h" 59 #include "net/cookies/cookie_monster.h" 60 #include "net/cookies/cookie_store.h" 61 #include "net/dns/mock_host_resolver.h" 62 #include "net/test/embedded_test_server/embedded_test_server.h" 63 #include "net/test/embedded_test_server/http_request.h" 64 #include "net/test/embedded_test_server/http_response.h" 65 #include "net/url_request/url_request_context.h" 66 #include "net/url_request/url_request_context_getter.h" 67 #include "policy/policy_constants.h" 68 #include "policy/proto/device_management_backend.pb.h" 69 #include "testing/gmock/include/gmock/gmock.h" 70 #include "testing/gtest/include/gtest/gtest.h" 71 #include "ui/base/l10n/l10n_util.h" 72 #include "url/gurl.h" 73 74 namespace em = enterprise_management; 75 76 using net::test_server::BasicHttpResponse; 77 using net::test_server::HttpRequest; 78 using net::test_server::HttpResponse; 79 using testing::_; 80 using testing::Return; 81 82 namespace chromeos { 83 84 namespace { 85 86 const char kGAIASIDCookieName[] = "SID"; 87 const char kGAIALSIDCookieName[] = "LSID"; 88 89 const char kTestAuthSIDCookie1[] = "fake-auth-SID-cookie-1"; 90 const char kTestAuthSIDCookie2[] = "fake-auth-SID-cookie-2"; 91 const char kTestAuthLSIDCookie1[] = "fake-auth-LSID-cookie-1"; 92 const char kTestAuthLSIDCookie2[] = "fake-auth-LSID-cookie-2"; 93 94 const char kFirstSAMLUserEmail[] = "bob (at) example.com"; 95 const char kSecondSAMLUserEmail[] = "alice (at) example.com"; 96 const char kHTTPSAMLUserEmail[] = "carol (at) example.com"; 97 const char kNonSAMLUserEmail[] = "dan (at) example.com"; 98 const char kDifferentDomainSAMLUserEmail[] = "eve (at) example.test"; 99 100 const char kSAMLIdPCookieName[] = "saml"; 101 const char kSAMLIdPCookieValue1[] = "value-1"; 102 const char kSAMLIdPCookieValue2[] = "value-2"; 103 104 const char kRelayState[] = "RelayState"; 105 106 // FakeSamlIdp serves IdP auth form and the form submission. The form is 107 // served with the template's RelayState placeholder expanded to the real 108 // RelayState parameter from request. The form submission redirects back to 109 // FakeGaia with the same RelayState. 110 class FakeSamlIdp { 111 public: 112 FakeSamlIdp(); 113 ~FakeSamlIdp(); 114 115 void SetUp(const std::string& base_path, const GURL& gaia_url); 116 117 void SetLoginHTMLTemplate(const std::string& template_file); 118 void SetLoginAuthHTMLTemplate(const std::string& template_file); 119 void SetRefreshURL(const GURL& refresh_url); 120 void SetCookieValue(const std::string& cookie_value); 121 122 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request); 123 124 private: 125 scoped_ptr<HttpResponse> BuildHTMLResponse(const std::string& html_template, 126 const std::string& relay_state, 127 const std::string& next_path); 128 129 base::FilePath html_template_dir_; 130 131 std::string login_path_; 132 std::string login_auth_path_; 133 134 std::string login_html_template_; 135 std::string login_auth_html_template_; 136 GURL gaia_assertion_url_; 137 GURL refresh_url_; 138 std::string cookie_value_; 139 140 DISALLOW_COPY_AND_ASSIGN(FakeSamlIdp); 141 }; 142 143 FakeSamlIdp::FakeSamlIdp() { 144 } 145 146 FakeSamlIdp::~FakeSamlIdp() { 147 } 148 149 void FakeSamlIdp::SetUp(const std::string& base_path, const GURL& gaia_url) { 150 base::FilePath test_data_dir; 151 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); 152 html_template_dir_ = test_data_dir.Append("login"); 153 154 login_path_= base_path; 155 login_auth_path_ = base_path + "Auth"; 156 gaia_assertion_url_ = gaia_url.Resolve("/SSO"); 157 } 158 159 void FakeSamlIdp::SetLoginHTMLTemplate(const std::string& template_file) { 160 EXPECT_TRUE(base::ReadFileToString( 161 html_template_dir_.Append(template_file), 162 &login_html_template_)); 163 } 164 165 void FakeSamlIdp::SetLoginAuthHTMLTemplate(const std::string& template_file) { 166 EXPECT_TRUE(base::ReadFileToString( 167 html_template_dir_.Append(template_file), 168 &login_auth_html_template_)); 169 } 170 171 void FakeSamlIdp::SetRefreshURL(const GURL& refresh_url) { 172 refresh_url_ = refresh_url; 173 } 174 175 void FakeSamlIdp::SetCookieValue(const std::string& cookie_value) { 176 cookie_value_ = cookie_value; 177 } 178 179 scoped_ptr<HttpResponse> FakeSamlIdp::HandleRequest( 180 const HttpRequest& request) { 181 // The scheme and host of the URL is actually not important but required to 182 // get a valid GURL in order to parse |request.relative_url|. 183 GURL request_url = GURL("http://localhost").Resolve(request.relative_url); 184 std::string request_path = request_url.path(); 185 186 if (request_path == login_path_) { 187 std::string relay_state; 188 net::GetValueForKeyInQuery(request_url, kRelayState, &relay_state); 189 return BuildHTMLResponse(login_html_template_, 190 relay_state, 191 login_auth_path_); 192 } 193 194 if (request_path != login_auth_path_) { 195 // Request not understood. 196 return scoped_ptr<HttpResponse>(); 197 } 198 199 std::string relay_state; 200 FakeGaia::GetQueryParameter(request.content, kRelayState, &relay_state); 201 GURL redirect_url = gaia_assertion_url_; 202 203 if (!login_auth_html_template_.empty()) { 204 return BuildHTMLResponse(login_auth_html_template_, 205 relay_state, 206 redirect_url.spec()); 207 } 208 209 redirect_url = net::AppendQueryParameter( 210 redirect_url, "SAMLResponse", "fake_response"); 211 redirect_url = net::AppendQueryParameter( 212 redirect_url, kRelayState, relay_state); 213 214 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); 215 http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); 216 http_response->AddCustomHeader("Location", redirect_url.spec()); 217 http_response->AddCustomHeader( 218 "Set-cookie", 219 base::StringPrintf("saml=%s", cookie_value_.c_str())); 220 return http_response.PassAs<HttpResponse>(); 221 } 222 223 scoped_ptr<HttpResponse> FakeSamlIdp::BuildHTMLResponse( 224 const std::string& html_template, 225 const std::string& relay_state, 226 const std::string& next_path) { 227 std::string response_html = html_template; 228 ReplaceSubstringsAfterOffset(&response_html, 0, "$RelayState", relay_state); 229 ReplaceSubstringsAfterOffset(&response_html, 0, "$Post", next_path); 230 ReplaceSubstringsAfterOffset( 231 &response_html, 0, "$Refresh", refresh_url_.spec()); 232 233 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); 234 http_response->set_code(net::HTTP_OK); 235 http_response->set_content(response_html); 236 http_response->set_content_type("text/html"); 237 238 return http_response.PassAs<HttpResponse>(); 239 } 240 241 } // namespace 242 243 class SamlTest : public InProcessBrowserTest { 244 public: 245 SamlTest() : saml_load_injected_(false) {} 246 virtual ~SamlTest() {} 247 248 virtual void SetUp() OVERRIDE { 249 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 250 251 // Start the GAIA https wrapper here so that the GAIA URLs can be pointed at 252 // it in SetUpCommandLine(). 253 gaia_https_forwarder_.reset( 254 new HTTPSForwarder(embedded_test_server()->base_url())); 255 ASSERT_TRUE(gaia_https_forwarder_->Start()); 256 257 // Start the SAML IdP https wrapper here so that GAIA can be pointed at it 258 // in SetUpCommandLine(). 259 saml_https_forwarder_.reset( 260 new HTTPSForwarder(embedded_test_server()->base_url())); 261 ASSERT_TRUE(saml_https_forwarder_->Start()); 262 263 // Stop IO thread here because no threads are allowed while 264 // spawning sandbox host process. See crbug.com/322732. 265 embedded_test_server()->StopThread(); 266 267 InProcessBrowserTest::SetUp(); 268 } 269 270 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 271 host_resolver()->AddRule("*", "127.0.0.1"); 272 } 273 274 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 275 command_line->AppendSwitch(switches::kLoginManager); 276 command_line->AppendSwitch(switches::kForceLoginManagerInTests); 277 command_line->AppendSwitch(::switches::kDisableBackgroundNetworking); 278 command_line->AppendSwitchASCII(switches::kLoginProfile, "user"); 279 280 const GURL gaia_url = gaia_https_forwarder_->GetURL(""); 281 command_line->AppendSwitchASCII(::switches::kGaiaUrl, gaia_url.spec()); 282 command_line->AppendSwitchASCII(::switches::kLsoUrl, gaia_url.spec()); 283 command_line->AppendSwitchASCII(::switches::kGoogleApisUrl, 284 gaia_url.spec()); 285 286 const GURL saml_idp_url = saml_https_forwarder_->GetURL("SAML"); 287 fake_saml_idp_.SetUp(saml_idp_url.path(), gaia_url); 288 fake_gaia_.RegisterSamlUser(kFirstSAMLUserEmail, saml_idp_url); 289 fake_gaia_.RegisterSamlUser(kSecondSAMLUserEmail, saml_idp_url); 290 fake_gaia_.RegisterSamlUser( 291 kHTTPSAMLUserEmail, 292 embedded_test_server()->base_url().Resolve("/SAML")); 293 fake_gaia_.RegisterSamlUser(kDifferentDomainSAMLUserEmail, saml_idp_url); 294 295 fake_gaia_.Initialize(); 296 } 297 298 virtual void SetUpOnMainThread() OVERRIDE { 299 fake_gaia_.SetFakeMergeSessionParams(kFirstSAMLUserEmail, 300 kTestAuthSIDCookie1, 301 kTestAuthLSIDCookie1); 302 303 embedded_test_server()->RegisterRequestHandler( 304 base::Bind(&FakeGaia::HandleRequest, base::Unretained(&fake_gaia_))); 305 embedded_test_server()->RegisterRequestHandler(base::Bind( 306 &FakeSamlIdp::HandleRequest, base::Unretained(&fake_saml_idp_))); 307 308 // Restart the thread as the sandbox host process has already been spawned. 309 embedded_test_server()->RestartThreadAndListen(); 310 311 login_screen_load_observer_.reset(new content::WindowedNotificationObserver( 312 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, 313 content::NotificationService::AllSources())); 314 } 315 316 virtual void TearDownOnMainThread() OVERRIDE { 317 // If the login display is still showing, exit gracefully. 318 if (LoginDisplayHostImpl::default_host()) { 319 base::MessageLoop::current()->PostTask(FROM_HERE, 320 base::Bind(&chrome::AttemptExit)); 321 content::RunMessageLoop(); 322 } 323 } 324 325 WebUILoginDisplay* GetLoginDisplay() { 326 ExistingUserController* controller = 327 ExistingUserController::current_controller(); 328 CHECK(controller); 329 return static_cast<WebUILoginDisplay*>(controller->login_display()); 330 } 331 332 void WaitForSigninScreen() { 333 WizardController* wizard_controller = 334 WizardController::default_controller(); 335 if (wizard_controller) { 336 WizardController::SkipPostLoginScreensForTesting(); 337 wizard_controller->SkipToLoginForTesting(LoginScreenContext()); 338 } 339 340 login_screen_load_observer_->Wait(); 341 } 342 343 void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) { 344 WaitForSigninScreen(); 345 346 if (!saml_load_injected_) { 347 saml_load_injected_ = true; 348 349 ASSERT_TRUE(content::ExecuteScript( 350 GetLoginUI()->GetWebContents(), 351 "$('gaia-signin').gaiaAuthHost_.addEventListener('authFlowChange'," 352 "function() {" 353 "window.domAutomationController.setAutomationId(0);" 354 "window.domAutomationController.send(" 355 "$('gaia-signin').isSAML() ? 'SamlLoaded' : 'GaiaLoaded');" 356 "});")); 357 } 358 359 content::DOMMessageQueue message_queue; // Start observe before SAML. 360 GetLoginDisplay()->ShowSigninScreenForCreds(gaia_email, ""); 361 362 std::string message; 363 ASSERT_TRUE(message_queue.WaitForMessage(&message)); 364 EXPECT_EQ("\"SamlLoaded\"", message); 365 } 366 367 void SetSignFormField(const std::string& field_id, 368 const std::string& field_value) { 369 std::string js = 370 "(function(){" 371 "document.getElementById('$FieldId').value = '$FieldValue';" 372 "var e = new Event('input');" 373 "document.getElementById('$FieldId').dispatchEvent(e);" 374 "})();"; 375 ReplaceSubstringsAfterOffset(&js, 0, "$FieldId", field_id); 376 ReplaceSubstringsAfterOffset(&js, 0, "$FieldValue", field_value); 377 ExecuteJsInSigninFrame(js); 378 } 379 380 void SendConfirmPassword(const std::string& password_to_confirm) { 381 std::string js = 382 "$('confirm-password-input').value='$Password';" 383 "$('confirm-password').onConfirmPassword_();"; 384 ReplaceSubstringsAfterOffset(&js, 0, "$Password", password_to_confirm); 385 ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(), js)); 386 } 387 388 void JsExpect(const std::string& js) { 389 bool result; 390 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( 391 GetLoginUI()->GetWebContents(), 392 "window.domAutomationController.send(!!(" + js + "));", 393 &result)); 394 EXPECT_TRUE(result) << js; 395 } 396 397 std::string WaitForAndGetFatalErrorMessage() { 398 OobeScreenWaiter(OobeDisplay::SCREEN_FATAL_ERROR).Wait(); 399 std::string error_message; 400 if (!content::ExecuteScriptAndExtractString( 401 GetLoginUI()->GetWebContents(), 402 "window.domAutomationController.send(" 403 "$('fatal-error-message').textContent);", 404 &error_message)) { 405 ADD_FAILURE(); 406 } 407 return error_message; 408 } 409 410 content::WebUI* GetLoginUI() { 411 return static_cast<LoginDisplayHostImpl*>( 412 LoginDisplayHostImpl::default_host())->GetOobeUI()->web_ui(); 413 } 414 415 // Executes JavaScript code in the auth iframe hosted by gaia_auth extension. 416 void ExecuteJsInSigninFrame(const std::string& js) { 417 content::RenderFrameHost* frame = InlineLoginUI::GetAuthIframe( 418 GetLoginUI()->GetWebContents(), GURL(), "signin-frame"); 419 ASSERT_TRUE(content::ExecuteScript(frame, js)); 420 } 421 422 FakeSamlIdp* fake_saml_idp() { return &fake_saml_idp_; } 423 424 protected: 425 scoped_ptr<content::WindowedNotificationObserver> login_screen_load_observer_; 426 FakeGaia fake_gaia_; 427 428 private: 429 FakeSamlIdp fake_saml_idp_; 430 scoped_ptr<HTTPSForwarder> gaia_https_forwarder_; 431 scoped_ptr<HTTPSForwarder> saml_https_forwarder_; 432 433 bool saml_load_injected_; 434 435 DISALLOW_COPY_AND_ASSIGN(SamlTest); 436 }; 437 438 // Tests that signin frame should have 'saml' class and 'cancel' button is 439 // visible when SAML IdP page is loaded. And 'cancel' button goes back to 440 // gaia on clicking. 441 IN_PROC_BROWSER_TEST_F(SamlTest, SamlUI) { 442 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 443 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 444 445 // Saml flow UI expectations. 446 JsExpect("$('gaia-signin').classList.contains('saml')"); 447 JsExpect("!$('cancel-add-user-button').hidden"); 448 449 // Click on 'cancel'. 450 content::DOMMessageQueue message_queue; // Observe before 'cancel'. 451 ASSERT_TRUE(content::ExecuteScript( 452 GetLoginUI()->GetWebContents(), 453 "$('cancel-add-user-button').click();")); 454 455 // Auth flow should change back to Gaia. 456 std::string message; 457 do { 458 ASSERT_TRUE(message_queue.WaitForMessage(&message)); 459 } while (message != "\"GaiaLoaded\""); 460 461 // Saml flow is gone. 462 JsExpect("!$('gaia-signin').classList.contains('saml')"); 463 } 464 465 // Tests the sign-in flow when the credentials passing API is used. 466 IN_PROC_BROWSER_TEST_F(SamlTest, CredentialPassingAPI) { 467 fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html"); 468 fake_saml_idp()->SetLoginAuthHTMLTemplate("saml_api_login_auth.html"); 469 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 470 471 // Fill-in the SAML IdP form and submit. 472 SetSignFormField("Email", "fake_user"); 473 SetSignFormField("Password", "fake_password"); 474 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 475 476 // Login should finish login and a session should start. 477 content::WindowedNotificationObserver( 478 chrome::NOTIFICATION_SESSION_STARTED, 479 content::NotificationService::AllSources()).Wait(); 480 } 481 482 // Tests the single password scraped flow. 483 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedSingle) { 484 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 485 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 486 487 // Fill-in the SAML IdP form and submit. 488 SetSignFormField("Email", "fake_user"); 489 SetSignFormField("Password", "fake_password"); 490 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 491 492 // Lands on confirm password screen. 493 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 494 495 // Enter an unknown password should go back to confirm password screen. 496 SendConfirmPassword("wrong_password"); 497 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 498 499 // Enter a known password should finish login and start session. 500 SendConfirmPassword("fake_password"); 501 content::WindowedNotificationObserver( 502 chrome::NOTIFICATION_SESSION_STARTED, 503 content::NotificationService::AllSources()).Wait(); 504 } 505 506 // Tests the multiple password scraped flow. 507 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedMultiple) { 508 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_two_passwords.html"); 509 510 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 511 512 SetSignFormField("Email", "fake_user"); 513 SetSignFormField("Password", "fake_password"); 514 SetSignFormField("Password1", "password1"); 515 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 516 517 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 518 519 // Either scraped password should be able to sign-in. 520 SendConfirmPassword("password1"); 521 content::WindowedNotificationObserver( 522 chrome::NOTIFICATION_SESSION_STARTED, 523 content::NotificationService::AllSources()).Wait(); 524 } 525 526 // Tests the no password scraped flow. 527 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedNone) { 528 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_no_passwords.html"); 529 530 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 531 532 SetSignFormField("Email", "fake_user"); 533 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 534 535 EXPECT_EQ(l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_NO_PASSWORD), 536 WaitForAndGetFatalErrorMessage()); 537 } 538 539 // Types |bob (at) example.com| into the GAIA login form but then authenticates as 540 // |alice (at) example.com| via SAML. Verifies that the logged-in user is correctly 541 // identified as Alice. 542 IN_PROC_BROWSER_TEST_F(SamlTest, UseAutenticatedUserEmailAddress) { 543 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 544 // Type |bob (at) example.com| into the GAIA login form. 545 StartSamlAndWaitForIdpPageLoad(kSecondSAMLUserEmail); 546 547 // Authenticate as alice (at) example.com via SAML (the |Email| provided here is 548 // irrelevant - the authenticated user's e-mail address that FakeGAIA 549 // reports was set via |SetFakeMergeSessionParams|. 550 SetSignFormField("Email", "fake_user"); 551 SetSignFormField("Password", "fake_password"); 552 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 553 554 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 555 556 SendConfirmPassword("fake_password"); 557 content::WindowedNotificationObserver( 558 chrome::NOTIFICATION_SESSION_STARTED, 559 content::NotificationService::AllSources()).Wait(); 560 const user_manager::User* user = 561 user_manager::UserManager::Get()->GetActiveUser(); 562 ASSERT_TRUE(user); 563 EXPECT_EQ(kFirstSAMLUserEmail, user->email()); 564 } 565 566 // Verifies that if the authenticated user's e-mail address cannot be retrieved, 567 // an error message is shown. 568 IN_PROC_BROWSER_TEST_F(SamlTest, FailToRetrieveAutenticatedUserEmailAddress) { 569 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 570 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 571 572 fake_gaia_.SetFakeMergeSessionParams( 573 "", kTestAuthSIDCookie1, kTestAuthLSIDCookie1); 574 SetSignFormField("Email", "fake_user"); 575 SetSignFormField("Password", "fake_password"); 576 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 577 578 EXPECT_EQ(l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_NO_EMAIL), 579 WaitForAndGetFatalErrorMessage()); 580 } 581 582 // Tests the password confirm flow: show error on the first failure and 583 // fatal error on the second failure. 584 IN_PROC_BROWSER_TEST_F(SamlTest, PasswordConfirmFlow) { 585 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 586 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 587 588 // Fill-in the SAML IdP form and submit. 589 SetSignFormField("Email", "fake_user"); 590 SetSignFormField("Password", "fake_password"); 591 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 592 593 // Lands on confirm password screen with no error message. 594 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 595 JsExpect("!$('confirm-password').classList.contains('error')"); 596 597 // Enter an unknown password for the first time should go back to confirm 598 // password screen with error message. 599 SendConfirmPassword("wrong_password"); 600 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 601 JsExpect("$('confirm-password').classList.contains('error')"); 602 603 // Enter an unknown password 2nd time should go back fatal error message. 604 SendConfirmPassword("wrong_password"); 605 EXPECT_EQ( 606 l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_PASSWORD_VERIFICATION), 607 WaitForAndGetFatalErrorMessage()); 608 } 609 610 // Verifies that when GAIA attempts to redirect to a SAML IdP served over http, 611 // not https, the redirect is blocked and an error message is shown. 612 IN_PROC_BROWSER_TEST_F(SamlTest, HTTPRedirectDisallowed) { 613 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 614 615 WaitForSigninScreen(); 616 GetLoginDisplay()->ShowSigninScreenForCreds(kHTTPSAMLUserEmail, ""); 617 618 const GURL url = embedded_test_server()->base_url().Resolve("/SAML"); 619 EXPECT_EQ(l10n_util::GetStringFUTF8( 620 IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL, 621 base::UTF8ToUTF16(url.spec())), 622 WaitForAndGetFatalErrorMessage()); 623 } 624 625 // Verifies that when GAIA attempts to redirect to a page served over http, not 626 // https, via an HTML meta refresh, the redirect is blocked and an error message 627 // is shown. This guards against regressions of http://crbug.com/359515. 628 IN_PROC_BROWSER_TEST_F(SamlTest, MetaRefreshToHTTPDisallowed) { 629 const GURL url = embedded_test_server()->base_url().Resolve("/SSO"); 630 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_instant_meta_refresh.html"); 631 fake_saml_idp()->SetRefreshURL(url); 632 633 WaitForSigninScreen(); 634 GetLoginDisplay()->ShowSigninScreenForCreds(kFirstSAMLUserEmail, ""); 635 636 EXPECT_EQ(l10n_util::GetStringFUTF8( 637 IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL, 638 base::UTF8ToUTF16(url.spec())), 639 WaitForAndGetFatalErrorMessage()); 640 } 641 642 class SAMLPolicyTest : public SamlTest { 643 public: 644 SAMLPolicyTest(); 645 virtual ~SAMLPolicyTest(); 646 647 // SamlTest: 648 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE; 649 virtual void SetUpOnMainThread() OVERRIDE; 650 651 void SetSAMLOfflineSigninTimeLimitPolicy(int limit); 652 void EnableTransferSAMLCookiesPolicy(); 653 654 void ShowGAIALoginForm(); 655 void LogInWithSAML(const std::string& user_id, 656 const std::string& auth_sid_cookie, 657 const std::string& auth_lsid_cookie); 658 659 std::string GetCookieValue(const std::string& name); 660 661 void GetCookies(); 662 663 protected: 664 void GetCookiesOnIOThread( 665 const scoped_refptr<net::URLRequestContextGetter>& request_context, 666 const base::Closure& callback); 667 void StoreCookieList(const base::Closure& callback, 668 const net::CookieList& cookie_list); 669 670 policy::DevicePolicyCrosTestHelper test_helper_; 671 672 // FakeDBusThreadManager uses FakeSessionManagerClient. 673 FakeSessionManagerClient* fake_session_manager_client_; 674 policy::DevicePolicyBuilder* device_policy_; 675 676 policy::MockConfigurationPolicyProvider provider_; 677 678 net::CookieList cookie_list_; 679 680 private: 681 DISALLOW_COPY_AND_ASSIGN(SAMLPolicyTest); 682 }; 683 684 SAMLPolicyTest::SAMLPolicyTest() 685 : fake_session_manager_client_(new FakeSessionManagerClient), 686 device_policy_(test_helper_.device_policy()) { 687 } 688 689 SAMLPolicyTest::~SAMLPolicyTest() { 690 } 691 692 void SAMLPolicyTest::SetUpInProcessBrowserTestFixture() { 693 DBusThreadManager::GetSetterForTesting()->SetSessionManagerClient( 694 scoped_ptr<SessionManagerClient>(fake_session_manager_client_)); 695 696 SamlTest::SetUpInProcessBrowserTestFixture(); 697 698 // Initialize device policy. 699 test_helper_.InstallOwnerKey(); 700 test_helper_.MarkAsEnterpriseOwned(); 701 device_policy_->SetDefaultSigningKey(); 702 device_policy_->Build(); 703 fake_session_manager_client_->set_device_policy(device_policy_->GetBlob()); 704 fake_session_manager_client_->OnPropertyChangeComplete(true); 705 706 // Initialize user policy. 707 EXPECT_CALL(provider_, IsInitializationComplete(_)) 708 .WillRepeatedly(Return(true)); 709 policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_); 710 } 711 712 void SAMLPolicyTest::SetUpOnMainThread() { 713 SamlTest::SetUpOnMainThread(); 714 715 // Pretend that the test users' OAuth tokens are valid. 716 user_manager::UserManager::Get()->SaveUserOAuthStatus( 717 kFirstSAMLUserEmail, user_manager::User::OAUTH2_TOKEN_STATUS_VALID); 718 user_manager::UserManager::Get()->SaveUserOAuthStatus( 719 kNonSAMLUserEmail, user_manager::User::OAUTH2_TOKEN_STATUS_VALID); 720 user_manager::UserManager::Get()->SaveUserOAuthStatus( 721 kDifferentDomainSAMLUserEmail, 722 user_manager::User::OAUTH2_TOKEN_STATUS_VALID); 723 } 724 725 void SAMLPolicyTest::SetSAMLOfflineSigninTimeLimitPolicy(int limit) { 726 policy::PolicyMap user_policy; 727 user_policy.Set(policy::key::kSAMLOfflineSigninTimeLimit, 728 policy::POLICY_LEVEL_MANDATORY, 729 policy::POLICY_SCOPE_USER, 730 new base::FundamentalValue(limit), 731 NULL); 732 provider_.UpdateChromePolicy(user_policy); 733 base::RunLoop().RunUntilIdle(); 734 } 735 736 void SAMLPolicyTest::EnableTransferSAMLCookiesPolicy() { 737 em::ChromeDeviceSettingsProto& proto(device_policy_->payload()); 738 proto.mutable_saml_settings()->set_transfer_saml_cookies(true); 739 740 base::RunLoop run_loop; 741 scoped_ptr<CrosSettings::ObserverSubscription> observer = 742 CrosSettings::Get()->AddSettingsObserver( 743 kAccountsPrefTransferSAMLCookies, 744 run_loop.QuitClosure()); 745 device_policy_->SetDefaultSigningKey(); 746 device_policy_->Build(); 747 fake_session_manager_client_->set_device_policy(device_policy_->GetBlob()); 748 fake_session_manager_client_->OnPropertyChangeComplete(true); 749 run_loop.Run(); 750 } 751 752 void SAMLPolicyTest::ShowGAIALoginForm() { 753 login_screen_load_observer_->Wait(); 754 ASSERT_TRUE(content::ExecuteScript( 755 GetLoginUI()->GetWebContents(), 756 "$('gaia-signin').gaiaAuthHost_.addEventListener('ready', function() {" 757 " window.domAutomationController.setAutomationId(0);" 758 " window.domAutomationController.send('ready');" 759 "});" 760 "$('add-user-button').click();")); 761 content::DOMMessageQueue message_queue; 762 std::string message; 763 ASSERT_TRUE(message_queue.WaitForMessage(&message)); 764 EXPECT_EQ("\"ready\"", message); 765 } 766 767 void SAMLPolicyTest::LogInWithSAML(const std::string& user_id, 768 const std::string& auth_sid_cookie, 769 const std::string& auth_lsid_cookie) { 770 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 771 StartSamlAndWaitForIdpPageLoad(user_id); 772 773 fake_gaia_.SetFakeMergeSessionParams( 774 user_id, auth_sid_cookie, auth_lsid_cookie); 775 SetSignFormField("Email", "fake_user"); 776 SetSignFormField("Password", "fake_password"); 777 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 778 779 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 780 781 SendConfirmPassword("fake_password"); 782 content::WindowedNotificationObserver( 783 chrome::NOTIFICATION_SESSION_STARTED, 784 content::NotificationService::AllSources()).Wait(); 785 } 786 787 std::string SAMLPolicyTest::GetCookieValue(const std::string& name) { 788 for (net::CookieList::const_iterator it = cookie_list_.begin(); 789 it != cookie_list_.end(); ++it) { 790 if (it->Name() == name) 791 return it->Value(); 792 } 793 return std::string(); 794 } 795 796 void SAMLPolicyTest::GetCookies() { 797 Profile* profile = chromeos::ProfileHelper::Get()->GetProfileByUserUnsafe( 798 user_manager::UserManager::Get()->GetActiveUser()); 799 ASSERT_TRUE(profile); 800 base::RunLoop run_loop; 801 content::BrowserThread::PostTask( 802 content::BrowserThread::IO, 803 FROM_HERE, 804 base::Bind(&SAMLPolicyTest::GetCookiesOnIOThread, 805 base::Unretained(this), 806 scoped_refptr<net::URLRequestContextGetter>( 807 profile->GetRequestContext()), 808 run_loop.QuitClosure())); 809 run_loop.Run(); 810 } 811 812 void SAMLPolicyTest::GetCookiesOnIOThread( 813 const scoped_refptr<net::URLRequestContextGetter>& request_context, 814 const base::Closure& callback) { 815 request_context->GetURLRequestContext()->cookie_store()-> 816 GetCookieMonster()->GetAllCookiesAsync(base::Bind( 817 &SAMLPolicyTest::StoreCookieList, 818 base::Unretained(this), 819 callback)); 820 } 821 822 void SAMLPolicyTest::StoreCookieList( 823 const base::Closure& callback, 824 const net::CookieList& cookie_list) { 825 cookie_list_ = cookie_list; 826 content::BrowserThread::PostTask(content::BrowserThread::UI, 827 FROM_HERE, 828 callback); 829 } 830 831 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_NoSAML) { 832 // Set the offline login time limit for SAML users to zero. 833 SetSAMLOfflineSigninTimeLimitPolicy(0); 834 835 WaitForSigninScreen(); 836 837 // Log in without SAML. 838 GetLoginDisplay()->ShowSigninScreenForCreds(kNonSAMLUserEmail, "password"); 839 840 content::WindowedNotificationObserver( 841 chrome::NOTIFICATION_SESSION_STARTED, 842 content::NotificationService::AllSources()).Wait(); 843 } 844 845 // Verifies that the offline login time limit does not affect a user who 846 // authenticated without SAML. 847 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, NoSAML) { 848 login_screen_load_observer_->Wait(); 849 // Verify that offline login is allowed. 850 JsExpect("window.getComputedStyle(document.querySelector(" 851 " '#pod-row .signin-button-container')).display == 'none'"); 852 } 853 854 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLNoLimit) { 855 // Remove the offline login time limit for SAML users. 856 SetSAMLOfflineSigninTimeLimitPolicy(-1); 857 858 LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1); 859 } 860 861 // Verifies that when no offline login time limit is set, a user who 862 // authenticated with SAML is allowed to log in offline. 863 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLNoLimit) { 864 login_screen_load_observer_->Wait(); 865 // Verify that offline login is allowed. 866 JsExpect("window.getComputedStyle(document.querySelector(" 867 " '#pod-row .signin-button-container')).display == 'none'"); 868 } 869 870 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLZeroLimit) { 871 // Set the offline login time limit for SAML users to zero. 872 SetSAMLOfflineSigninTimeLimitPolicy(0); 873 874 LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1); 875 } 876 877 // Verifies that when the offline login time limit is exceeded for a user who 878 // authenticated via SAML, that user is forced to log in online the next time. 879 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLZeroLimit) { 880 login_screen_load_observer_->Wait(); 881 // Verify that offline login is not allowed. 882 JsExpect("window.getComputedStyle(document.querySelector(" 883 " '#pod-row .signin-button-container')).display != 'none'"); 884 } 885 886 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_PRE_TransferCookiesAffiliated) { 887 fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue1); 888 LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie1, kTestAuthLSIDCookie1); 889 890 GetCookies(); 891 EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName)); 892 EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName)); 893 EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName)); 894 } 895 896 // Verifies that when the DeviceTransferSAMLCookies policy is not enabled, SAML 897 // IdP cookies are not transferred to a user's profile on subsequent login, even 898 // if the user belongs to the domain that the device is enrolled into. Also 899 // verifies that GAIA cookies are not transferred. 900 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_TransferCookiesAffiliated) { 901 fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2); 902 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 903 ShowGAIALoginForm(); 904 LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie2, kTestAuthLSIDCookie2); 905 906 GetCookies(); 907 EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName)); 908 EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName)); 909 EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName)); 910 } 911 912 // Verifies that when the DeviceTransferSAMLCookies policy is enabled, SAML IdP 913 // cookies are transferred to a user's profile on subsequent login when the user 914 // belongs to the domain that the device is enrolled into. Also verifies that 915 // GAIA cookies are not transferred. 916 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, TransferCookiesAffiliated) { 917 fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2); 918 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 919 ShowGAIALoginForm(); 920 921 EnableTransferSAMLCookiesPolicy(); 922 LogInWithSAML(kFirstSAMLUserEmail, kTestAuthSIDCookie2, kTestAuthLSIDCookie2); 923 924 GetCookies(); 925 EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName)); 926 EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName)); 927 EXPECT_EQ(kSAMLIdPCookieValue2, GetCookieValue(kSAMLIdPCookieName)); 928 } 929 930 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_TransferCookiesUnaffiliated) { 931 fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue1); 932 LogInWithSAML(kDifferentDomainSAMLUserEmail, 933 kTestAuthSIDCookie1, 934 kTestAuthLSIDCookie1); 935 936 GetCookies(); 937 EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName)); 938 EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName)); 939 EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName)); 940 } 941 942 // Verifies that even if the DeviceTransferSAMLCookies policy is enabled, SAML 943 // IdP are not transferred to a user's profile on subsequent login if the user 944 // does not belong to the domain that the device is enrolled into. Also verifies 945 // that GAIA cookies are not transferred. 946 IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, TransferCookiesUnaffiliated) { 947 fake_saml_idp()->SetCookieValue(kSAMLIdPCookieValue2); 948 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 949 ShowGAIALoginForm(); 950 951 EnableTransferSAMLCookiesPolicy(); 952 LogInWithSAML(kDifferentDomainSAMLUserEmail, 953 kTestAuthSIDCookie1, 954 kTestAuthLSIDCookie1); 955 956 GetCookies(); 957 EXPECT_EQ(kTestAuthSIDCookie1, GetCookieValue(kGAIASIDCookieName)); 958 EXPECT_EQ(kTestAuthLSIDCookie1, GetCookieValue(kGAIALSIDCookieName)); 959 EXPECT_EQ(kSAMLIdPCookieValue1, GetCookieValue(kSAMLIdPCookieName)); 960 } 961 962 } // namespace chromeos 963