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