saml_browsertest.cc revision 116680a4aac90f2aa7413d9095a592090648e557
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/command_line.h" 6#include "base/file_util.h" 7#include "base/files/file_path.h" 8#include "base/memory/scoped_ptr.h" 9#include "base/path_service.h" 10#include "base/run_loop.h" 11#include "base/strings/string16.h" 12#include "base/strings/string_util.h" 13#include "base/strings/utf_string_conversions.h" 14#include "base/values.h" 15#include "chrome/browser/chrome_notification_types.h" 16#include "chrome/browser/chromeos/login/existing_user_controller.h" 17#include "chrome/browser/chromeos/login/test/https_forwarder.h" 18#include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h" 19#include "chrome/browser/chromeos/login/ui/login_display_host_impl.h" 20#include "chrome/browser/chromeos/login/ui/webui_login_display.h" 21#include "chrome/browser/chromeos/login/users/user.h" 22#include "chrome/browser/chromeos/login/users/user_manager.h" 23#include "chrome/browser/chromeos/login/wizard_controller.h" 24#include "chrome/browser/lifetime/application_lifetime.h" 25#include "chrome/common/chrome_paths.h" 26#include "chrome/common/chrome_switches.h" 27#include "chrome/test/base/in_process_browser_test.h" 28#include "chromeos/chromeos_switches.h" 29#include "components/policy/core/browser/browser_policy_connector.h" 30#include "components/policy/core/common/mock_configuration_policy_provider.h" 31#include "components/policy/core/common/policy_map.h" 32#include "components/policy/core/common/policy_types.h" 33#include "content/public/browser/web_contents.h" 34#include "content/public/test/browser_test_utils.h" 35#include "content/public/test/test_utils.h" 36#include "google_apis/gaia/fake_gaia.h" 37#include "google_apis/gaia/gaia_switches.h" 38#include "grit/generated_resources.h" 39#include "net/base/url_util.h" 40#include "net/dns/mock_host_resolver.h" 41#include "net/test/embedded_test_server/embedded_test_server.h" 42#include "net/test/embedded_test_server/http_request.h" 43#include "net/test/embedded_test_server/http_response.h" 44#include "policy/policy_constants.h" 45#include "testing/gmock/include/gmock/gmock.h" 46#include "testing/gtest/include/gtest/gtest.h" 47#include "ui/base/l10n/l10n_util.h" 48#include "url/gurl.h" 49 50using net::test_server::BasicHttpResponse; 51using net::test_server::HttpRequest; 52using net::test_server::HttpResponse; 53using testing::_; 54using testing::Return; 55 56namespace chromeos { 57 58namespace { 59 60const char kTestAuthSIDCookie[] = "fake-auth-SID-cookie"; 61const char kTestAuthLSIDCookie[] = "fake-auth-LSID-cookie"; 62const char kTestAuthCode[] = "fake-auth-code"; 63const char kTestGaiaUberToken[] = "fake-uber-token"; 64const char kTestAuthLoginAccessToken[] = "fake-access-token"; 65const char kTestRefreshToken[] = "fake-refresh-token"; 66const char kTestSessionSIDCookie[] = "fake-session-SID-cookie"; 67const char kTestSessionLSIDCookie[] = "fake-session-LSID-cookie"; 68 69const char kFirstSAMLUserEmail[] = "bob@example.com"; 70const char kSecondSAMLUserEmail[] = "alice@example.com"; 71const char kHTTPSAMLUserEmail[] = "carol@example.com"; 72const char kNonSAMLUserEmail[] = "dan@example.com"; 73 74const char kRelayState[] = "RelayState"; 75 76// FakeSamlIdp serves IdP auth form and the form submission. The form is 77// served with the template's RelayState placeholder expanded to the real 78// RelayState parameter from request. The form submission redirects back to 79// FakeGaia with the same RelayState. 80class FakeSamlIdp { 81 public: 82 FakeSamlIdp(); 83 ~FakeSamlIdp(); 84 85 void SetUp(const std::string& base_path, const GURL& gaia_url); 86 87 void SetLoginHTMLTemplate(const std::string& template_file); 88 void SetLoginAuthHTMLTemplate(const std::string& template_file); 89 void SetRefreshURL(const GURL& refresh_url); 90 91 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request); 92 93 private: 94 scoped_ptr<HttpResponse> BuildHTMLResponse(const std::string& html_template, 95 const std::string& relay_state, 96 const std::string& next_path); 97 98 base::FilePath html_template_dir_; 99 100 std::string login_path_; 101 std::string login_auth_path_; 102 103 std::string login_html_template_; 104 std::string login_auth_html_template_; 105 GURL gaia_assertion_url_; 106 GURL refresh_url_; 107 108 DISALLOW_COPY_AND_ASSIGN(FakeSamlIdp); 109}; 110 111FakeSamlIdp::FakeSamlIdp() { 112} 113 114FakeSamlIdp::~FakeSamlIdp() { 115} 116 117void FakeSamlIdp::SetUp(const std::string& base_path, const GURL& gaia_url) { 118 base::FilePath test_data_dir; 119 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir)); 120 html_template_dir_ = test_data_dir.Append("login"); 121 122 login_path_= base_path; 123 login_auth_path_ = base_path + "Auth"; 124 gaia_assertion_url_ = gaia_url.Resolve("/SSO"); 125} 126 127void FakeSamlIdp::SetLoginHTMLTemplate(const std::string& template_file) { 128 EXPECT_TRUE(base::ReadFileToString( 129 html_template_dir_.Append(template_file), 130 &login_html_template_)); 131} 132 133void FakeSamlIdp::SetLoginAuthHTMLTemplate(const std::string& template_file) { 134 EXPECT_TRUE(base::ReadFileToString( 135 html_template_dir_.Append(template_file), 136 &login_auth_html_template_)); 137} 138 139void FakeSamlIdp::SetRefreshURL(const GURL& refresh_url) { 140 refresh_url_ = refresh_url; 141} 142 143scoped_ptr<HttpResponse> FakeSamlIdp::HandleRequest( 144 const HttpRequest& request) { 145 // The scheme and host of the URL is actually not important but required to 146 // get a valid GURL in order to parse |request.relative_url|. 147 GURL request_url = GURL("http://localhost").Resolve(request.relative_url); 148 std::string request_path = request_url.path(); 149 150 if (request_path == login_path_) { 151 std::string relay_state; 152 net::GetValueForKeyInQuery(request_url, kRelayState, &relay_state); 153 return BuildHTMLResponse(login_html_template_, 154 relay_state, 155 login_auth_path_); 156 } 157 158 if (request_path != login_auth_path_) { 159 // Request not understood. 160 return scoped_ptr<HttpResponse>(); 161 } 162 163 std::string relay_state; 164 FakeGaia::GetQueryParameter(request.content, kRelayState, &relay_state); 165 GURL redirect_url = gaia_assertion_url_; 166 167 if (!login_auth_html_template_.empty()) { 168 return BuildHTMLResponse(login_auth_html_template_, 169 relay_state, 170 redirect_url.spec()); 171 } 172 173 redirect_url = net::AppendQueryParameter( 174 redirect_url, "SAMLResponse", "fake_response"); 175 redirect_url = net::AppendQueryParameter( 176 redirect_url, kRelayState, relay_state); 177 178 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); 179 http_response->set_code(net::HTTP_TEMPORARY_REDIRECT); 180 http_response->AddCustomHeader("Location", redirect_url.spec()); 181 return http_response.PassAs<HttpResponse>(); 182} 183 184scoped_ptr<HttpResponse> FakeSamlIdp::BuildHTMLResponse( 185 const std::string& html_template, 186 const std::string& relay_state, 187 const std::string& next_path) { 188 std::string response_html = html_template; 189 ReplaceSubstringsAfterOffset(&response_html, 0, "$RelayState", relay_state); 190 ReplaceSubstringsAfterOffset(&response_html, 0, "$Post", next_path); 191 ReplaceSubstringsAfterOffset( 192 &response_html, 0, "$Refresh", refresh_url_.spec()); 193 194 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse()); 195 http_response->set_code(net::HTTP_OK); 196 http_response->set_content(response_html); 197 http_response->set_content_type("text/html"); 198 199 return http_response.PassAs<HttpResponse>(); 200} 201 202} // namespace 203 204class SamlTest : public InProcessBrowserTest { 205 public: 206 SamlTest() : saml_load_injected_(false) {} 207 virtual ~SamlTest() {} 208 209 virtual void SetUp() OVERRIDE { 210 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady()); 211 212 // Start the GAIA https wrapper here so that the GAIA URLs can be pointed at 213 // it in SetUpCommandLine(). 214 gaia_https_forwarder_.reset( 215 new HTTPSForwarder(embedded_test_server()->base_url())); 216 ASSERT_TRUE(gaia_https_forwarder_->Start()); 217 218 // Start the SAML IdP https wrapper here so that GAIA can be pointed at it 219 // in SetUpCommandLine(). 220 saml_https_forwarder_.reset( 221 new HTTPSForwarder(embedded_test_server()->base_url())); 222 ASSERT_TRUE(saml_https_forwarder_->Start()); 223 224 // Stop IO thread here because no threads are allowed while 225 // spawning sandbox host process. See crbug.com/322732. 226 embedded_test_server()->StopThread(); 227 228 InProcessBrowserTest::SetUp(); 229 } 230 231 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE { 232 host_resolver()->AddRule("*", "127.0.0.1"); 233 } 234 235 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE { 236 command_line->AppendSwitch(switches::kLoginManager); 237 command_line->AppendSwitch(switches::kForceLoginManagerInTests); 238 command_line->AppendSwitch(::switches::kDisableBackgroundNetworking); 239 command_line->AppendSwitchASCII(switches::kLoginProfile, "user"); 240 241 const GURL gaia_url = gaia_https_forwarder_->GetURL(""); 242 command_line->AppendSwitchASCII(::switches::kGaiaUrl, gaia_url.spec()); 243 command_line->AppendSwitchASCII(::switches::kLsoUrl, gaia_url.spec()); 244 command_line->AppendSwitchASCII(::switches::kGoogleApisUrl, 245 gaia_url.spec()); 246 247 const GURL saml_idp_url = saml_https_forwarder_->GetURL("SAML"); 248 fake_saml_idp_.SetUp(saml_idp_url.path(), gaia_url); 249 fake_gaia_.RegisterSamlUser(kFirstSAMLUserEmail, saml_idp_url); 250 fake_gaia_.RegisterSamlUser(kSecondSAMLUserEmail, saml_idp_url); 251 fake_gaia_.RegisterSamlUser( 252 kHTTPSAMLUserEmail, 253 embedded_test_server()->base_url().Resolve("/SAML")); 254 255 fake_gaia_.Initialize(); 256 } 257 258 virtual void SetUpOnMainThread() OVERRIDE { 259 SetMergeSessionParams(kFirstSAMLUserEmail); 260 261 embedded_test_server()->RegisterRequestHandler( 262 base::Bind(&FakeGaia::HandleRequest, base::Unretained(&fake_gaia_))); 263 embedded_test_server()->RegisterRequestHandler(base::Bind( 264 &FakeSamlIdp::HandleRequest, base::Unretained(&fake_saml_idp_))); 265 266 // Restart the thread as the sandbox host process has already been spawned. 267 embedded_test_server()->RestartThreadAndListen(); 268 269 login_screen_load_observer_.reset(new content::WindowedNotificationObserver( 270 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE, 271 content::NotificationService::AllSources())); 272 } 273 274 virtual void CleanUpOnMainThread() OVERRIDE { 275 // If the login display is still showing, exit gracefully. 276 if (LoginDisplayHostImpl::default_host()) { 277 base::MessageLoop::current()->PostTask(FROM_HERE, 278 base::Bind(&chrome::AttemptExit)); 279 content::RunMessageLoop(); 280 } 281 } 282 283 void SetMergeSessionParams(const std::string& email) { 284 FakeGaia::MergeSessionParams params; 285 params.auth_sid_cookie = kTestAuthSIDCookie; 286 params.auth_lsid_cookie = kTestAuthLSIDCookie; 287 params.auth_code = kTestAuthCode; 288 params.refresh_token = kTestRefreshToken; 289 params.access_token = kTestAuthLoginAccessToken; 290 params.gaia_uber_token = kTestGaiaUberToken; 291 params.session_sid_cookie = kTestSessionSIDCookie; 292 params.session_lsid_cookie = kTestSessionLSIDCookie; 293 params.email = email; 294 fake_gaia_.SetMergeSessionParams(params); 295 } 296 297 WebUILoginDisplay* GetLoginDisplay() { 298 ExistingUserController* controller = 299 ExistingUserController::current_controller(); 300 CHECK(controller); 301 return static_cast<WebUILoginDisplay*>(controller->login_display()); 302 } 303 304 void WaitForSigninScreen() { 305 WizardController::SkipPostLoginScreensForTesting(); 306 WizardController* wizard_controller = 307 chromeos::WizardController::default_controller(); 308 CHECK(wizard_controller); 309 wizard_controller->SkipToLoginForTesting(LoginScreenContext()); 310 311 login_screen_load_observer_->Wait(); 312 } 313 314 void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) { 315 WaitForSigninScreen(); 316 317 if (!saml_load_injected_) { 318 saml_load_injected_ = true; 319 320 ASSERT_TRUE(content::ExecuteScript( 321 GetLoginUI()->GetWebContents(), 322 "$('gaia-signin').gaiaAuthHost_.addEventListener('authFlowChange'," 323 "function() {" 324 "window.domAutomationController.setAutomationId(0);" 325 "window.domAutomationController.send(" 326 "$('gaia-signin').isSAML() ? 'SamlLoaded' : 'GaiaLoaded');" 327 "});")); 328 } 329 330 content::DOMMessageQueue message_queue; // Start observe before SAML. 331 GetLoginDisplay()->ShowSigninScreenForCreds(gaia_email, ""); 332 333 std::string message; 334 ASSERT_TRUE(message_queue.WaitForMessage(&message)); 335 EXPECT_EQ("\"SamlLoaded\"", message); 336 } 337 338 void SetSignFormField(const std::string& field_id, 339 const std::string& field_value) { 340 std::string js = 341 "(function(){" 342 "document.getElementById('$FieldId').value = '$FieldValue';" 343 "var e = new Event('input');" 344 "document.getElementById('$FieldId').dispatchEvent(e);" 345 "})();"; 346 ReplaceSubstringsAfterOffset(&js, 0, "$FieldId", field_id); 347 ReplaceSubstringsAfterOffset(&js, 0, "$FieldValue", field_value); 348 ExecuteJsInSigninFrame(js); 349 } 350 351 void SendConfirmPassword(const std::string& password_to_confirm) { 352 std::string js = 353 "$('confirm-password-input').value='$Password';" 354 "$('confirm-password').onConfirmPassword_();"; 355 ReplaceSubstringsAfterOffset(&js, 0, "$Password", password_to_confirm); 356 ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(), js)); 357 } 358 359 void JsExpect(const std::string& js) { 360 bool result; 361 EXPECT_TRUE(content::ExecuteScriptAndExtractBool( 362 GetLoginUI()->GetWebContents(), 363 "window.domAutomationController.send(!!(" + js + "));", 364 &result)); 365 EXPECT_TRUE(result) << js; 366 } 367 368 std::string WaitForAndGetFatalErrorMessage() { 369 OobeScreenWaiter(OobeDisplay::SCREEN_FATAL_ERROR).Wait(); 370 std::string error_message; 371 if (!content::ExecuteScriptAndExtractString( 372 GetLoginUI()->GetWebContents(), 373 "window.domAutomationController.send(" 374 "$('fatal-error-message').textContent);", 375 &error_message)) { 376 ADD_FAILURE(); 377 } 378 return error_message; 379 } 380 381 content::WebUI* GetLoginUI() { 382 return static_cast<LoginDisplayHostImpl*>( 383 LoginDisplayHostImpl::default_host())->GetOobeUI()->web_ui(); 384 } 385 386 // Executes JavaScript code in the auth iframe hosted by gaia_auth extension. 387 void ExecuteJsInSigninFrame(const std::string& js) { 388 content::RenderFrameHost* frame = 389 LoginDisplayHostImpl::GetGaiaAuthIframe(GetLoginUI()->GetWebContents()); 390 ASSERT_TRUE(content::ExecuteScript(frame, js)); 391 } 392 393 FakeSamlIdp* fake_saml_idp() { return &fake_saml_idp_; } 394 395 protected: 396 scoped_ptr<content::WindowedNotificationObserver> login_screen_load_observer_; 397 398 private: 399 FakeGaia fake_gaia_; 400 FakeSamlIdp fake_saml_idp_; 401 scoped_ptr<HTTPSForwarder> gaia_https_forwarder_; 402 scoped_ptr<HTTPSForwarder> saml_https_forwarder_; 403 404 bool saml_load_injected_; 405 406 DISALLOW_COPY_AND_ASSIGN(SamlTest); 407}; 408 409// Tests that signin frame should have 'saml' class and 'cancel' button is 410// visible when SAML IdP page is loaded. And 'cancel' button goes back to 411// gaia on clicking. 412IN_PROC_BROWSER_TEST_F(SamlTest, SamlUI) { 413 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 414 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 415 416 // Saml flow UI expectations. 417 JsExpect("$('gaia-signin').classList.contains('saml')"); 418 JsExpect("!$('cancel-add-user-button').hidden"); 419 420 // Click on 'cancel'. 421 content::DOMMessageQueue message_queue; // Observe before 'cancel'. 422 ASSERT_TRUE(content::ExecuteScript( 423 GetLoginUI()->GetWebContents(), 424 "$('cancel-add-user-button').click();")); 425 426 // Auth flow should change back to Gaia. 427 std::string message; 428 do { 429 ASSERT_TRUE(message_queue.WaitForMessage(&message)); 430 } while (message != "\"GaiaLoaded\""); 431 432 // Saml flow is gone. 433 JsExpect("!$('gaia-signin').classList.contains('saml')"); 434} 435 436// Tests the sign-in flow when the credentials passing API is used. 437IN_PROC_BROWSER_TEST_F(SamlTest, CredentialPassingAPI) { 438 fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html"); 439 fake_saml_idp()->SetLoginAuthHTMLTemplate("saml_api_login_auth.html"); 440 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 441 442 // Fill-in the SAML IdP form and submit. 443 SetSignFormField("Email", "fake_user"); 444 SetSignFormField("Password", "fake_password"); 445 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 446 447 // Login should finish login and a session should start. 448 content::WindowedNotificationObserver( 449 chrome::NOTIFICATION_SESSION_STARTED, 450 content::NotificationService::AllSources()).Wait(); 451} 452 453// Tests the single password scraped flow. 454IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedSingle) { 455 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 456 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 457 458 // Fill-in the SAML IdP form and submit. 459 SetSignFormField("Email", "fake_user"); 460 SetSignFormField("Password", "fake_password"); 461 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 462 463 // Lands on confirm password screen. 464 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 465 466 // Enter an unknown password should go back to confirm password screen. 467 SendConfirmPassword("wrong_password"); 468 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 469 470 // Enter a known password should finish login and start session. 471 SendConfirmPassword("fake_password"); 472 content::WindowedNotificationObserver( 473 chrome::NOTIFICATION_SESSION_STARTED, 474 content::NotificationService::AllSources()).Wait(); 475} 476 477// Tests the multiple password scraped flow. 478IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedMultiple) { 479 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_two_passwords.html"); 480 481 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 482 483 SetSignFormField("Email", "fake_user"); 484 SetSignFormField("Password", "fake_password"); 485 SetSignFormField("Password1", "password1"); 486 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 487 488 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 489 490 // Either scraped password should be able to sign-in. 491 SendConfirmPassword("password1"); 492 content::WindowedNotificationObserver( 493 chrome::NOTIFICATION_SESSION_STARTED, 494 content::NotificationService::AllSources()).Wait(); 495} 496 497// Tests the no password scraped flow. 498IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedNone) { 499 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_no_passwords.html"); 500 501 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 502 503 SetSignFormField("Email", "fake_user"); 504 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 505 506 EXPECT_EQ(l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_NO_PASSWORD), 507 WaitForAndGetFatalErrorMessage()); 508} 509 510// Types |bob@example.com| into the GAIA login form but then authenticates as 511// |alice@example.com| via SAML. Verifies that the logged-in user is correctly 512// identified as Alice. 513IN_PROC_BROWSER_TEST_F(SamlTest, UseAutenticatedUserEmailAddress) { 514 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 515 // Type |bob@example.com| into the GAIA login form. 516 StartSamlAndWaitForIdpPageLoad(kSecondSAMLUserEmail); 517 518 // Authenticate as alice@example.com via SAML (the |Email| provided here is 519 // irrelevant - the authenticated user's e-mail address that FakeGAIA 520 // reports was set via SetMergeSessionParams()). 521 SetSignFormField("Email", "fake_user"); 522 SetSignFormField("Password", "fake_password"); 523 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 524 525 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 526 527 SendConfirmPassword("fake_password"); 528 content::WindowedNotificationObserver( 529 chrome::NOTIFICATION_SESSION_STARTED, 530 content::NotificationService::AllSources()).Wait(); 531 const User* user = UserManager::Get()->GetActiveUser(); 532 ASSERT_TRUE(user); 533 EXPECT_EQ(kFirstSAMLUserEmail, user->email()); 534} 535 536// Verifies that if the authenticated user's e-mail address cannot be retrieved, 537// an error message is shown. 538IN_PROC_BROWSER_TEST_F(SamlTest, FailToRetrieveAutenticatedUserEmailAddress) { 539 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 540 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 541 542 SetMergeSessionParams(""); 543 SetSignFormField("Email", "fake_user"); 544 SetSignFormField("Password", "fake_password"); 545 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 546 547 EXPECT_EQ(l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_NO_EMAIL), 548 WaitForAndGetFatalErrorMessage()); 549} 550 551// Tests the password confirm flow: show error on the first failure and 552// fatal error on the second failure. 553IN_PROC_BROWSER_TEST_F(SamlTest, PasswordConfirmFlow) { 554 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 555 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 556 557 // Fill-in the SAML IdP form and submit. 558 SetSignFormField("Email", "fake_user"); 559 SetSignFormField("Password", "fake_password"); 560 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 561 562 // Lands on confirm password screen with no error message. 563 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 564 JsExpect("!$('confirm-password').classList.contains('error')"); 565 566 // Enter an unknown password for the first time should go back to confirm 567 // password screen with error message. 568 SendConfirmPassword("wrong_password"); 569 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 570 JsExpect("$('confirm-password').classList.contains('error')"); 571 572 // Enter an unknown password 2nd time should go back fatal error message. 573 SendConfirmPassword("wrong_password"); 574 EXPECT_EQ( 575 l10n_util::GetStringUTF8(IDS_LOGIN_FATAL_ERROR_PASSWORD_VERIFICATION), 576 WaitForAndGetFatalErrorMessage()); 577} 578 579// Verifies that when GAIA attempts to redirect to a SAML IdP served over http, 580// not https, the redirect is blocked and an error message is shown. 581IN_PROC_BROWSER_TEST_F(SamlTest, HTTPRedirectDisallowed) { 582 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 583 584 WaitForSigninScreen(); 585 GetLoginDisplay()->ShowSigninScreenForCreds(kHTTPSAMLUserEmail, ""); 586 587 const GURL url = embedded_test_server()->base_url().Resolve("/SAML"); 588 EXPECT_EQ(l10n_util::GetStringFUTF8( 589 IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL, 590 base::UTF8ToUTF16(url.spec())), 591 WaitForAndGetFatalErrorMessage()); 592} 593 594// Verifies that when GAIA attempts to redirect to a page served over http, not 595// https, via an HTML meta refresh, the redirect is blocked and an error message 596// is shown. This guards against regressions of http://crbug.com/359515. 597IN_PROC_BROWSER_TEST_F(SamlTest, MetaRefreshToHTTPDisallowed) { 598 const GURL url = embedded_test_server()->base_url().Resolve("/SSO"); 599 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_instant_meta_refresh.html"); 600 fake_saml_idp()->SetRefreshURL(url); 601 602 WaitForSigninScreen(); 603 GetLoginDisplay()->ShowSigninScreenForCreds(kFirstSAMLUserEmail, ""); 604 605 EXPECT_EQ(l10n_util::GetStringFUTF8( 606 IDS_LOGIN_FATAL_ERROR_TEXT_INSECURE_URL, 607 base::UTF8ToUTF16(url.spec())), 608 WaitForAndGetFatalErrorMessage()); 609} 610 611class SAMLPolicyTest : public SamlTest { 612 public: 613 SAMLPolicyTest(); 614 virtual ~SAMLPolicyTest(); 615 616 // SamlTest: 617 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE; 618 virtual void SetUpOnMainThread() OVERRIDE; 619 620 void SetSAMLOfflineSigninTimeLimitPolicy(int limit); 621 622 protected: 623 policy::MockConfigurationPolicyProvider provider_; 624 625 private: 626 DISALLOW_COPY_AND_ASSIGN(SAMLPolicyTest); 627}; 628 629SAMLPolicyTest::SAMLPolicyTest() { 630} 631 632SAMLPolicyTest::~SAMLPolicyTest() { 633} 634 635void SAMLPolicyTest::SetUpInProcessBrowserTestFixture() { 636 SamlTest::SetUpInProcessBrowserTestFixture(); 637 638 EXPECT_CALL(provider_, IsInitializationComplete(_)) 639 .WillRepeatedly(Return(true)); 640 policy::BrowserPolicyConnector::SetPolicyProviderForTesting(&provider_); 641} 642 643void SAMLPolicyTest::SetUpOnMainThread() { 644 SamlTest::SetUpOnMainThread(); 645 646 // Pretend that the test users' OAuth tokens are valid. 647 UserManager::Get()->SaveUserOAuthStatus(kFirstSAMLUserEmail, 648 User::OAUTH2_TOKEN_STATUS_VALID); 649 UserManager::Get()->SaveUserOAuthStatus(kNonSAMLUserEmail, 650 User::OAUTH2_TOKEN_STATUS_VALID); 651} 652 653void SAMLPolicyTest::SetSAMLOfflineSigninTimeLimitPolicy(int limit) { 654 policy::PolicyMap policy; 655 policy.Set(policy::key::kSAMLOfflineSigninTimeLimit, 656 policy::POLICY_LEVEL_MANDATORY, 657 policy::POLICY_SCOPE_USER, 658 new base::FundamentalValue(limit), 659 NULL); 660 provider_.UpdateChromePolicy(policy); 661 base::RunLoop().RunUntilIdle(); 662} 663 664IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_NoSAML) { 665 // Set the offline login time limit for SAML users to zero. 666 SetSAMLOfflineSigninTimeLimitPolicy(0); 667 668 WaitForSigninScreen(); 669 670 // Log in without SAML. 671 GetLoginDisplay()->ShowSigninScreenForCreds(kNonSAMLUserEmail, "password"); 672 673 content::WindowedNotificationObserver( 674 chrome::NOTIFICATION_SESSION_STARTED, 675 content::NotificationService::AllSources()).Wait(); 676} 677 678// Verifies that the offline login time limit does not affect a user who 679// authenticated without SAML. 680IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, NoSAML) { 681 login_screen_load_observer_->Wait(); 682 // Verify that offline login is allowed. 683 JsExpect("window.getComputedStyle(document.querySelector(" 684 " '#pod-row .signin-button-container')).display == 'none'"); 685} 686 687IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLNoLimit) { 688 // Remove the offline login time limit for SAML users. 689 SetSAMLOfflineSigninTimeLimitPolicy(-1); 690 691 // Log in with SAML. 692 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 693 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 694 695 SetSignFormField("Email", "fake_user"); 696 SetSignFormField("Password", "fake_password"); 697 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 698 699 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 700 701 SendConfirmPassword("fake_password"); 702 content::WindowedNotificationObserver( 703 chrome::NOTIFICATION_SESSION_STARTED, 704 content::NotificationService::AllSources()).Wait(); 705} 706 707// Verifies that when no offline login time limit is set, a user who 708// authenticated with SAML is allowed to log in offline. 709IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLNoLimit) { 710 login_screen_load_observer_->Wait(); 711 // Verify that offline login is allowed. 712 JsExpect("window.getComputedStyle(document.querySelector(" 713 " '#pod-row .signin-button-container')).display == 'none'"); 714} 715 716IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, PRE_SAMLZeroLimit) { 717 // Set the offline login time limit for SAML users to zero. 718 SetSAMLOfflineSigninTimeLimitPolicy(0); 719 720 // Log in with SAML. 721 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html"); 722 StartSamlAndWaitForIdpPageLoad(kFirstSAMLUserEmail); 723 724 SetSignFormField("Email", "fake_user"); 725 SetSignFormField("Password", "fake_password"); 726 ExecuteJsInSigninFrame("document.getElementById('Submit').click();"); 727 728 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait(); 729 730 SendConfirmPassword("fake_password"); 731 content::WindowedNotificationObserver( 732 chrome::NOTIFICATION_SESSION_STARTED, 733 content::NotificationService::AllSources()).Wait(); 734} 735 736// Verifies that when the offline login time limit is exceeded for a user who 737// authenticated via SAML, that user is forced to log in online the next time. 738IN_PROC_BROWSER_TEST_F(SAMLPolicyTest, SAMLZeroLimit) { 739 login_screen_load_observer_->Wait(); 740 // Verify that offline login is not allowed. 741 JsExpect("window.getComputedStyle(document.querySelector(" 742 " '#pod-row .signin-button-container')).display != 'none'"); 743} 744 745} // namespace chromeos 746