Disable view source for Developer Tools.
[chromium-blink-merge.git] / chrome / browser / chromeos / login / saml_browsertest.cc
blobb866206751fd68378ec3c1ca3dee52a8776249fd
1 // Copyright 2013 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.
5 #include "base/command_line.h"
6 #include "base/file_util.h"
7 #include "base/files/file_path.h"
8 #include "base/path_service.h"
9 #include "base/strings/string_util.h"
10 #include "base/strings/utf_string_conversions.h"
11 #include "chrome/browser/chrome_notification_types.h"
12 #include "chrome/browser/chromeos/login/existing_user_controller.h"
13 #include "chrome/browser/chromeos/login/login_display_host_impl.h"
14 #include "chrome/browser/chromeos/login/test/oobe_screen_waiter.h"
15 #include "chrome/browser/chromeos/login/user.h"
16 #include "chrome/browser/chromeos/login/user_manager.h"
17 #include "chrome/browser/chromeos/login/webui_login_display.h"
18 #include "chrome/browser/chromeos/login/wizard_controller.h"
19 #include "chrome/browser/lifetime/application_lifetime.h"
20 #include "chrome/common/chrome_paths.h"
21 #include "chrome/common/chrome_switches.h"
22 #include "chrome/test/base/in_process_browser_test.h"
23 #include "chromeos/chromeos_switches.h"
24 #include "content/public/browser/render_view_host.h"
25 #include "content/public/browser/web_contents.h"
26 #include "content/public/test/browser_test_utils.h"
27 #include "content/public/test/test_utils.h"
28 #include "google_apis/gaia/fake_gaia.h"
29 #include "google_apis/gaia/gaia_switches.h"
30 #include "net/base/url_util.h"
31 #include "net/dns/mock_host_resolver.h"
32 #include "net/test/embedded_test_server/embedded_test_server.h"
33 #include "net/test/embedded_test_server/http_request.h"
34 #include "net/test/embedded_test_server/http_response.h"
35 #include "testing/gtest/include/gtest/gtest.h"
37 using net::test_server::BasicHttpResponse;
38 using net::test_server::HttpRequest;
39 using net::test_server::HttpResponse;
41 namespace chromeos {
43 namespace {
45 const char kTestAuthSIDCookie[] = "fake-auth-SID-cookie";
46 const char kTestAuthLSIDCookie[] = "fake-auth-LSID-cookie";
47 const char kTestAuthCode[] = "fake-auth-code";
48 const char kTestGaiaUberToken[] = "fake-uber-token";
49 const char kTestAuthLoginAccessToken[] = "fake-access-token";
50 const char kTestRefreshToken[] = "fake-refresh-token";
51 const char kTestSessionSIDCookie[] = "fake-session-SID-cookie";
52 const char kTestSessionLSIDCookie[] = "fake-session-LSID-cookie";
54 const char kAnotherUserEmail[] = "alice@example.com";
55 const char kUserEmail[] = "bob@example.com";
57 const char kRelayState[] = "RelayState";
59 // FakeSamlIdp serves IdP auth form and the form submission. The form is
60 // served with the template's RelayState placeholder expanded to the real
61 // RelayState parameter from request. The form submission redirects back to
62 // FakeGaia with the same RelayState.
63 class FakeSamlIdp {
64 public:
65 FakeSamlIdp();
66 ~FakeSamlIdp();
68 void SetUp(const std::string& base_path, const GURL& gaia_url);
70 void SetLoginHTMLTemplate(const std::string& template_file);
71 void SetLoginAuthHTMLTemplate(const std::string& template_file);
73 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
75 private:
76 scoped_ptr<HttpResponse> BuildHTMLResponse(const std::string& html_template,
77 const std::string& relay_state,
78 const std::string& next_path);
80 base::FilePath html_template_dir_;
82 std::string login_path_;
83 std::string login_auth_path_;
85 std::string login_html_template_;
86 std::string login_auth_html_template_;
87 GURL gaia_assertion_url_;
89 DISALLOW_COPY_AND_ASSIGN(FakeSamlIdp);
92 FakeSamlIdp::FakeSamlIdp() {
95 FakeSamlIdp::~FakeSamlIdp() {
98 void FakeSamlIdp::SetUp(const std::string& base_path, const GURL& gaia_url) {
99 base::FilePath test_data_dir;
100 ASSERT_TRUE(PathService::Get(chrome::DIR_TEST_DATA, &test_data_dir));
101 html_template_dir_ = test_data_dir.Append("login");
103 login_path_= base_path;
104 login_auth_path_ = base_path + "Auth";
105 gaia_assertion_url_ = gaia_url.Resolve("/SSO");
108 void FakeSamlIdp::SetLoginHTMLTemplate(const std::string& template_file) {
109 EXPECT_TRUE(base::ReadFileToString(
110 html_template_dir_.Append(template_file),
111 &login_html_template_));
114 void FakeSamlIdp::SetLoginAuthHTMLTemplate(const std::string& template_file) {
115 EXPECT_TRUE(base::ReadFileToString(
116 html_template_dir_.Append(template_file),
117 &login_auth_html_template_));
120 scoped_ptr<HttpResponse> FakeSamlIdp::HandleRequest(
121 const HttpRequest& request) {
122 // The scheme and host of the URL is actually not important but required to
123 // get a valid GURL in order to parse |request.relative_url|.
124 GURL request_url = GURL("http://localhost").Resolve(request.relative_url);
125 std::string request_path = request_url.path();
127 if (request_path == login_path_) {
128 std::string relay_state;
129 net::GetValueForKeyInQuery(request_url, kRelayState, &relay_state);
130 return BuildHTMLResponse(login_html_template_,
131 relay_state,
132 login_auth_path_);
135 if (request_path != login_auth_path_) {
136 // Request not understood.
137 return scoped_ptr<HttpResponse>();
140 std::string relay_state;
141 FakeGaia::GetQueryParameter(request.content, kRelayState, &relay_state);
142 GURL redirect_url = gaia_assertion_url_;
144 if (!login_auth_html_template_.empty()) {
145 return BuildHTMLResponse(login_auth_html_template_,
146 relay_state,
147 redirect_url.spec());
150 redirect_url = net::AppendQueryParameter(
151 redirect_url, "SAMLResponse", "fake_response");
152 redirect_url = net::AppendQueryParameter(
153 redirect_url, kRelayState, relay_state);
155 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
156 http_response->set_code(net::HTTP_TEMPORARY_REDIRECT);
157 http_response->AddCustomHeader("Location", redirect_url.spec());
158 return http_response.PassAs<HttpResponse>();
161 scoped_ptr<HttpResponse> FakeSamlIdp::BuildHTMLResponse(
162 const std::string& html_template,
163 const std::string& relay_state,
164 const std::string& next_path) {
165 std::string response_html = html_template;
166 ReplaceSubstringsAfterOffset(&response_html, 0, "$RelayState", relay_state);
167 ReplaceSubstringsAfterOffset(&response_html, 0, "$Post", next_path);
169 scoped_ptr<BasicHttpResponse> http_response(new BasicHttpResponse());
170 http_response->set_code(net::HTTP_OK);
171 http_response->set_content(response_html);
172 http_response->set_content_type("text/html");
174 return http_response.PassAs<HttpResponse>();
177 } // namespace
179 class SamlTest : public InProcessBrowserTest {
180 public:
181 SamlTest() : saml_load_injected_(false) {}
182 virtual ~SamlTest() {}
184 virtual void SetUp() OVERRIDE {
185 // Start embedded test server here so that we can get server base url
186 // and override Gaia urls in SetupCommandLine.
187 ASSERT_TRUE(embedded_test_server()->InitializeAndWaitUntilReady());
189 // Stop IO thread here because no threads are allowed while
190 // spawning sandbox host process. See crbug.com/322732.
191 embedded_test_server()->StopThread();
193 InProcessBrowserTest::SetUp();
196 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
197 host_resolver()->AddRule("*", "127.0.0.1");
200 virtual void SetUpCommandLine(CommandLine* command_line) OVERRIDE {
201 command_line->AppendSwitch(switches::kLoginManager);
202 command_line->AppendSwitch(switches::kForceLoginManagerInTests);
203 command_line->AppendSwitch(::switches::kDisableBackgroundNetworking);
204 command_line->AppendSwitchASCII(switches::kLoginProfile, "user");
205 command_line->AppendSwitch(switches::kEnableSamlSignin);
207 const GURL& server_url = embedded_test_server()->base_url();
209 std::string gaia_host("gaia");
210 GURL::Replacements replace_gaia_host;
211 replace_gaia_host.SetHostStr(gaia_host);
212 gaia_url_ = server_url.ReplaceComponents(replace_gaia_host);
214 command_line->AppendSwitchASCII(::switches::kGaiaUrl, gaia_url_.spec());
215 command_line->AppendSwitchASCII(::switches::kLsoUrl, gaia_url_.spec());
216 command_line->AppendSwitchASCII(::switches::kGoogleApisUrl,
217 gaia_url_.spec());
218 fake_gaia_.Initialize();
220 std::string saml_idp_host("saml.idp");
221 GURL::Replacements replace_saml_idp_host;
222 replace_saml_idp_host.SetHostStr(saml_idp_host);
223 GURL saml_idp_url = server_url.ReplaceComponents(replace_saml_idp_host);
224 saml_idp_url = saml_idp_url.Resolve("/SAML/SSO");
226 fake_saml_idp_.SetUp(saml_idp_url.path(), gaia_url_);
227 fake_gaia_.RegisterSamlUser(kAnotherUserEmail, saml_idp_url);
228 fake_gaia_.RegisterSamlUser(kUserEmail, saml_idp_url);
231 virtual void SetUpOnMainThread() OVERRIDE {
232 FakeGaia::MergeSessionParams params;
233 params.auth_sid_cookie = kTestAuthSIDCookie;
234 params.auth_lsid_cookie = kTestAuthLSIDCookie;
235 params.auth_code = kTestAuthCode;
236 params.refresh_token = kTestRefreshToken;
237 params.access_token = kTestAuthLoginAccessToken;
238 params.gaia_uber_token = kTestGaiaUberToken;
239 params.session_sid_cookie = kTestSessionSIDCookie;
240 params.session_lsid_cookie = kTestSessionLSIDCookie;
241 params.email = kUserEmail;
242 fake_gaia_.SetMergeSessionParams(params);
244 embedded_test_server()->RegisterRequestHandler(
245 base::Bind(&FakeGaia::HandleRequest, base::Unretained(&fake_gaia_)));
246 embedded_test_server()->RegisterRequestHandler(base::Bind(
247 &FakeSamlIdp::HandleRequest, base::Unretained(&fake_saml_idp_)));
249 // Restart the thread as the sandbox host process has already been spawned.
250 embedded_test_server()->RestartThreadAndListen();
253 virtual void CleanUpOnMainThread() OVERRIDE {
254 // If the login display is still showing, exit gracefully.
255 if (LoginDisplayHostImpl::default_host()) {
256 base::MessageLoop::current()->PostTask(FROM_HERE,
257 base::Bind(&chrome::AttemptExit));
258 content::RunMessageLoop();
262 WebUILoginDisplay* GetLoginDisplay() {
263 ExistingUserController* controller =
264 ExistingUserController::current_controller();
265 CHECK(controller);
266 return static_cast<WebUILoginDisplay*>(controller->login_display());
269 void WaitForSigninScreen() {
270 WizardController::SkipPostLoginScreensForTesting();
271 WizardController* wizard_controller =
272 chromeos::WizardController::default_controller();
273 CHECK(wizard_controller);
274 wizard_controller->SkipToLoginForTesting(LoginScreenContext());
276 content::WindowedNotificationObserver(
277 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
278 content::NotificationService::AllSources()).Wait();
281 void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) {
282 WaitForSigninScreen();
284 if (!saml_load_injected_) {
285 saml_load_injected_ = true;
287 ASSERT_TRUE(content::ExecuteScript(
288 GetLoginUI()->GetWebContents(),
289 "$('gaia-signin').gaiaAuthHost_.addEventListener('authFlowChange',"
290 "function() {"
291 "window.domAutomationController.setAutomationId(0);"
292 "window.domAutomationController.send("
293 "$('gaia-signin').isSAML() ? 'SamlLoaded' : 'GaiaLoaded');"
294 "});"));
297 content::DOMMessageQueue message_queue; // Start observe before SAML.
298 GetLoginDisplay()->ShowSigninScreenForCreds(gaia_email, "");
300 std::string message;
301 ASSERT_TRUE(message_queue.WaitForMessage(&message));
302 EXPECT_EQ("\"SamlLoaded\"", message);
305 void SetSignFormField(const std::string& field_id,
306 const std::string& field_value) {
307 std::string js =
308 "(function(){"
309 "document.getElementById('$FieldId').value = '$FieldValue';"
310 "var e = new Event('input');"
311 "document.getElementById('$FieldId').dispatchEvent(e);"
312 "})();";
313 ReplaceSubstringsAfterOffset(&js, 0, "$FieldId", field_id);
314 ReplaceSubstringsAfterOffset(&js, 0, "$FieldValue", field_value);
315 ExecuteJsInSigninFrame(js);
318 void SendConfirmPassword(const std::string& password_to_confirm) {
319 std::string js =
320 "$('confirm-password-input').value='$Password';"
321 "$('confirm-password').onConfirmPassword_();";
322 ReplaceSubstringsAfterOffset(&js, 0, "$Password", password_to_confirm);
323 ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(), js));
326 void JsExpect(const std::string& js) {
327 bool result;
328 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
329 GetLoginUI()->GetWebContents(),
330 "window.domAutomationController.send(!!(" + js + "));",
331 &result));
332 EXPECT_TRUE(result) << js;
335 content::WebUI* GetLoginUI() {
336 return static_cast<chromeos::LoginDisplayHostImpl*>(
337 chromeos::LoginDisplayHostImpl::default_host())->GetOobeUI()->web_ui();
340 // Executes Js code in the auth iframe hosted by gaia_auth extension.
341 void ExecuteJsInSigninFrame(const std::string& js) {
342 ASSERT_TRUE(content::ExecuteScriptInFrame(
343 GetLoginUI()->GetWebContents(),
344 "//iframe[@id='signin-frame']\n//iframe",
345 js));
348 FakeSamlIdp* fake_saml_idp() { return &fake_saml_idp_; }
350 private:
351 GURL gaia_url_;
352 FakeGaia fake_gaia_;
353 FakeSamlIdp fake_saml_idp_;
355 bool saml_load_injected_;
357 DISALLOW_COPY_AND_ASSIGN(SamlTest);
360 // Tests that signin frame should have 'saml' class and 'cancel' button is
361 // visible when SAML IdP page is loaded. And 'cancel' button goes back to
362 // gaia on clicking.
363 IN_PROC_BROWSER_TEST_F(SamlTest, SamlUI) {
364 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
365 StartSamlAndWaitForIdpPageLoad(kUserEmail);
367 // Saml flow UI expectations.
368 JsExpect("$('gaia-signin').classList.contains('saml')");
369 JsExpect("!$('cancel-add-user-button').hidden");
371 // Click on 'cancel'.
372 content::DOMMessageQueue message_queue; // Observe before 'cancel'.
373 ASSERT_TRUE(content::ExecuteScript(
374 GetLoginUI()->GetWebContents(),
375 "$('cancel-add-user-button').click();"));
377 // Auth flow should change back to Gaia.
378 std::string message;
379 do {
380 ASSERT_TRUE(message_queue.WaitForMessage(&message));
381 } while (message != "\"GaiaLoaded\"");
383 // Saml flow is gone.
384 JsExpect("!$('gaia-signin').classList.contains('saml')");
387 // Tests the sign-in flow when the credentials passing API is used.
388 IN_PROC_BROWSER_TEST_F(SamlTest, CredentialPassingAPI) {
389 fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html");
390 fake_saml_idp()->SetLoginAuthHTMLTemplate("saml_api_login_auth.html");
391 StartSamlAndWaitForIdpPageLoad(kUserEmail);
393 // Fill-in the SAML IdP form and submit.
394 SetSignFormField("Email", "fake_user");
395 SetSignFormField("Password", "fake_password");
396 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
398 // Login should finish login and a session should start.
399 content::WindowedNotificationObserver(
400 chrome::NOTIFICATION_SESSION_STARTED,
401 content::NotificationService::AllSources()).Wait();
404 // Tests the single password scraped flow.
405 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedSingle) {
406 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
407 StartSamlAndWaitForIdpPageLoad(kUserEmail);
409 // Fill-in the SAML IdP form and submit.
410 SetSignFormField("Email", "fake_user");
411 SetSignFormField("Password", "fake_password");
412 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
414 // Lands on confirm password screen.
415 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
417 // Enter an unknown password should go back to confirm password screen.
418 SendConfirmPassword("wrong_password");
419 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
421 // Enter a known password should finish login and start session.
422 SendConfirmPassword("fake_password");
423 content::WindowedNotificationObserver(
424 chrome::NOTIFICATION_SESSION_STARTED,
425 content::NotificationService::AllSources()).Wait();
428 // Tests the multiple password scraped flow.
429 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedMultiple) {
430 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_two_passwords.html");
432 StartSamlAndWaitForIdpPageLoad(kUserEmail);
434 SetSignFormField("Email", "fake_user");
435 SetSignFormField("Password", "fake_password");
436 SetSignFormField("Password1", "password1");
437 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
439 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
441 // Either scraped password should be able to sign-in.
442 SendConfirmPassword("password1");
443 content::WindowedNotificationObserver(
444 chrome::NOTIFICATION_SESSION_STARTED,
445 content::NotificationService::AllSources()).Wait();
448 // Tests the no password scraped flow.
449 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedNone) {
450 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_no_passwords.html");
452 StartSamlAndWaitForIdpPageLoad(kUserEmail);
454 SetSignFormField("Email", "fake_user");
455 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
457 OobeScreenWaiter(OobeDisplay::SCREEN_MESSAGE_BOX).Wait();
458 JsExpect(
459 "$('message-box-title').textContent == "
460 "loadTimeData.getString('noPasswordWarningTitle')");
463 // Types |alice@example.com| into the GAIA login form but then authenticates as
464 // |bob@example.com| via SAML. Verifies that the logged-in user is correctly
465 // identified as Bob.
466 IN_PROC_BROWSER_TEST_F(SamlTest, UseAutenticatedUserEmailAddress) {
467 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
468 // Type |alice@example.com| into the GAIA login form.
469 StartSamlAndWaitForIdpPageLoad(kAnotherUserEmail);
471 // Authenticate as bob@example.com via SAML (the |Email| provided here is
472 // irrelevant - the authenticated user's e-mail address that FakeGAIA
473 // reports was set via SetMergeSessionParams()).
474 SetSignFormField("Email", "fake_user");
475 SetSignFormField("Password", "fake_password");
476 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
478 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
480 SendConfirmPassword("fake_password");
481 content::WindowedNotificationObserver(
482 chrome::NOTIFICATION_SESSION_STARTED,
483 content::NotificationService::AllSources()).Wait();
484 const User* user = UserManager::Get()->GetActiveUser();
485 ASSERT_TRUE(user);
486 EXPECT_EQ(kUserEmail, user->email());
490 } // namespace chromeos