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