Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(96)

Side by Side Diff: chrome/browser/chromeos/login/saml_browsertest.cc

Issue 143463009: Add policy that forces SAML users to log in online periodically (Closed) Base URL: svn://svn.chromium.org/chrome/trunk/src
Patch Set: Speculative fix for PolicyPrefIndicatorTest.CheckPolicyIndicators/3. Created 6 years, 10 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch | Annotate | Revision Log
OLDNEW
(Empty)
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.
4
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"
36
37 using net::test_server::BasicHttpResponse;
38 using net::test_server::HttpRequest;
39 using net::test_server::HttpResponse;
40
41 namespace chromeos {
42
43 namespace {
44
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";
53
54 const char kAnotherUserEmail[] = "alice@example.com";
55 const char kUserEmail[] = "bob@example.com";
56
57 const char kRelayState[] = "RelayState";
58
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();
67
68 void SetUp(const std::string& base_path, const GURL& gaia_url);
69
70 void SetLoginHTMLTemplate(const std::string& template_file);
71 void SetLoginAuthHTMLTemplate(const std::string& template_file);
72
73 scoped_ptr<HttpResponse> HandleRequest(const HttpRequest& request);
74
75 private:
76 scoped_ptr<HttpResponse> BuildHTMLResponse(const std::string& html_template,
77 const std::string& relay_state,
78 const std::string& next_path);
79
80 base::FilePath html_template_dir_;
81
82 std::string login_path_;
83 std::string login_auth_path_;
84
85 std::string login_html_template_;
86 std::string login_auth_html_template_;
87 GURL gaia_assertion_url_;
88
89 DISALLOW_COPY_AND_ASSIGN(FakeSamlIdp);
90 };
91
92 FakeSamlIdp::FakeSamlIdp() {
93 }
94
95 FakeSamlIdp::~FakeSamlIdp() {
96 }
97
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");
102
103 login_path_= base_path;
104 login_auth_path_ = base_path + "Auth";
105 gaia_assertion_url_ = gaia_url.Resolve("/SSO");
106 }
107
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_));
112 }
113
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_));
118 }
119
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();
126
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_);
133 }
134
135 if (request_path != login_auth_path_) {
136 // Request not understood.
137 return scoped_ptr<HttpResponse>();
138 }
139
140 std::string relay_state;
141 FakeGaia::GetQueryParameter(request.content, kRelayState, &relay_state);
142 GURL redirect_url = gaia_assertion_url_;
143
144 if (!login_auth_html_template_.empty()) {
145 return BuildHTMLResponse(login_auth_html_template_,
146 relay_state,
147 redirect_url.spec());
148 }
149
150 redirect_url = net::AppendQueryParameter(
151 redirect_url, "SAMLResponse", "fake_response");
152 redirect_url = net::AppendQueryParameter(
153 redirect_url, kRelayState, relay_state);
154
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>();
159 }
160
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);
168
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");
173
174 return http_response.PassAs<HttpResponse>();
175 }
176
177 } // namespace
178
179 class SamlTest : public InProcessBrowserTest {
180 public:
181 SamlTest() : saml_load_injected_(false) {}
182 virtual ~SamlTest() {}
183
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());
188
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();
192
193 InProcessBrowserTest::SetUp();
194 }
195
196 virtual void SetUpInProcessBrowserTestFixture() OVERRIDE {
197 host_resolver()->AddRule("*", "127.0.0.1");
198 }
199
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
206 const GURL& server_url = embedded_test_server()->base_url();
207
208 std::string gaia_host("gaia");
209 GURL::Replacements replace_gaia_host;
210 replace_gaia_host.SetHostStr(gaia_host);
211 gaia_url_ = server_url.ReplaceComponents(replace_gaia_host);
212
213 command_line->AppendSwitchASCII(::switches::kGaiaUrl, gaia_url_.spec());
214 command_line->AppendSwitchASCII(::switches::kLsoUrl, gaia_url_.spec());
215 command_line->AppendSwitchASCII(::switches::kGoogleApisUrl,
216 gaia_url_.spec());
217 fake_gaia_.Initialize();
218
219 std::string saml_idp_host("saml.idp");
220 GURL::Replacements replace_saml_idp_host;
221 replace_saml_idp_host.SetHostStr(saml_idp_host);
222 GURL saml_idp_url = server_url.ReplaceComponents(replace_saml_idp_host);
223 saml_idp_url = saml_idp_url.Resolve("/SAML/SSO");
224
225 fake_saml_idp_.SetUp(saml_idp_url.path(), gaia_url_);
226 fake_gaia_.RegisterSamlUser(kAnotherUserEmail, saml_idp_url);
227 fake_gaia_.RegisterSamlUser(kUserEmail, saml_idp_url);
228 }
229
230 virtual void SetUpOnMainThread() OVERRIDE {
231 FakeGaia::MergeSessionParams params;
232 params.auth_sid_cookie = kTestAuthSIDCookie;
233 params.auth_lsid_cookie = kTestAuthLSIDCookie;
234 params.auth_code = kTestAuthCode;
235 params.refresh_token = kTestRefreshToken;
236 params.access_token = kTestAuthLoginAccessToken;
237 params.gaia_uber_token = kTestGaiaUberToken;
238 params.session_sid_cookie = kTestSessionSIDCookie;
239 params.session_lsid_cookie = kTestSessionLSIDCookie;
240 params.email = kUserEmail;
241 fake_gaia_.SetMergeSessionParams(params);
242
243 embedded_test_server()->RegisterRequestHandler(
244 base::Bind(&FakeGaia::HandleRequest, base::Unretained(&fake_gaia_)));
245 embedded_test_server()->RegisterRequestHandler(base::Bind(
246 &FakeSamlIdp::HandleRequest, base::Unretained(&fake_saml_idp_)));
247
248 // Restart the thread as the sandbox host process has already been spawned.
249 embedded_test_server()->RestartThreadAndListen();
250 }
251
252 virtual void CleanUpOnMainThread() OVERRIDE {
253 // If the login display is still showing, exit gracefully.
254 if (LoginDisplayHostImpl::default_host()) {
255 base::MessageLoop::current()->PostTask(FROM_HERE,
256 base::Bind(&chrome::AttemptExit));
257 content::RunMessageLoop();
258 }
259 }
260
261 WebUILoginDisplay* GetLoginDisplay() {
262 ExistingUserController* controller =
263 ExistingUserController::current_controller();
264 CHECK(controller);
265 return static_cast<WebUILoginDisplay*>(controller->login_display());
266 }
267
268 void WaitForSigninScreen() {
269 WizardController::SkipPostLoginScreensForTesting();
270 WizardController* wizard_controller =
271 chromeos::WizardController::default_controller();
272 CHECK(wizard_controller);
273 wizard_controller->SkipToLoginForTesting(LoginScreenContext());
274
275 content::WindowedNotificationObserver(
276 chrome::NOTIFICATION_LOGIN_OR_LOCK_WEBUI_VISIBLE,
277 content::NotificationService::AllSources()).Wait();
278 }
279
280 void StartSamlAndWaitForIdpPageLoad(const std::string& gaia_email) {
281 WaitForSigninScreen();
282
283 if (!saml_load_injected_) {
284 saml_load_injected_ = true;
285
286 ASSERT_TRUE(content::ExecuteScript(
287 GetLoginUI()->GetWebContents(),
288 "$('gaia-signin').gaiaAuthHost_.addEventListener('authFlowChange',"
289 "function() {"
290 "window.domAutomationController.setAutomationId(0);"
291 "window.domAutomationController.send("
292 "$('gaia-signin').isSAML() ? 'SamlLoaded' : 'GaiaLoaded');"
293 "});"));
294 }
295
296 content::DOMMessageQueue message_queue; // Start observe before SAML.
297 GetLoginDisplay()->ShowSigninScreenForCreds(gaia_email, "");
298
299 std::string message;
300 ASSERT_TRUE(message_queue.WaitForMessage(&message));
301 EXPECT_EQ("\"SamlLoaded\"", message);
302 }
303
304 void SetSignFormField(const std::string& field_id,
305 const std::string& field_value) {
306 std::string js =
307 "(function(){"
308 "document.getElementById('$FieldId').value = '$FieldValue';"
309 "var e = new Event('input');"
310 "document.getElementById('$FieldId').dispatchEvent(e);"
311 "})();";
312 ReplaceSubstringsAfterOffset(&js, 0, "$FieldId", field_id);
313 ReplaceSubstringsAfterOffset(&js, 0, "$FieldValue", field_value);
314 ExecuteJsInSigninFrame(js);
315 }
316
317 void SendConfirmPassword(const std::string& password_to_confirm) {
318 std::string js =
319 "$('confirm-password-input').value='$Password';"
320 "$('confirm-password').onConfirmPassword_();";
321 ReplaceSubstringsAfterOffset(&js, 0, "$Password", password_to_confirm);
322 ASSERT_TRUE(content::ExecuteScript(GetLoginUI()->GetWebContents(), js));
323 }
324
325 void JsExpect(const std::string& js) {
326 bool result;
327 EXPECT_TRUE(content::ExecuteScriptAndExtractBool(
328 GetLoginUI()->GetWebContents(),
329 "window.domAutomationController.send(!!(" + js + "));",
330 &result));
331 EXPECT_TRUE(result) << js;
332 }
333
334 content::WebUI* GetLoginUI() {
335 return static_cast<chromeos::LoginDisplayHostImpl*>(
336 chromeos::LoginDisplayHostImpl::default_host())->GetOobeUI()->web_ui();
337 }
338
339 // Executes Js code in the auth iframe hosted by gaia_auth extension.
340 void ExecuteJsInSigninFrame(const std::string& js) {
341 ASSERT_TRUE(content::ExecuteScriptInFrame(
342 GetLoginUI()->GetWebContents(),
343 "//iframe[@id='signin-frame']\n//iframe",
344 js));
345 }
346
347 FakeSamlIdp* fake_saml_idp() { return &fake_saml_idp_; }
348
349 private:
350 GURL gaia_url_;
351 FakeGaia fake_gaia_;
352 FakeSamlIdp fake_saml_idp_;
353
354 bool saml_load_injected_;
355
356 DISALLOW_COPY_AND_ASSIGN(SamlTest);
357 };
358
359 // Tests that signin frame should have 'saml' class and 'cancel' button is
360 // visible when SAML IdP page is loaded. And 'cancel' button goes back to
361 // gaia on clicking.
362 IN_PROC_BROWSER_TEST_F(SamlTest, SamlUI) {
363 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
364 StartSamlAndWaitForIdpPageLoad(kUserEmail);
365
366 // Saml flow UI expectations.
367 JsExpect("$('gaia-signin').classList.contains('saml')");
368 JsExpect("!$('cancel-add-user-button').hidden");
369
370 // Click on 'cancel'.
371 content::DOMMessageQueue message_queue; // Observe before 'cancel'.
372 ASSERT_TRUE(content::ExecuteScript(
373 GetLoginUI()->GetWebContents(),
374 "$('cancel-add-user-button').click();"));
375
376 // Auth flow should change back to Gaia.
377 std::string message;
378 do {
379 ASSERT_TRUE(message_queue.WaitForMessage(&message));
380 } while (message != "\"GaiaLoaded\"");
381
382 // Saml flow is gone.
383 JsExpect("!$('gaia-signin').classList.contains('saml')");
384 }
385
386 // Tests the sign-in flow when the credentials passing API is used.
387 IN_PROC_BROWSER_TEST_F(SamlTest, CredentialPassingAPI) {
388 fake_saml_idp()->SetLoginHTMLTemplate("saml_api_login.html");
389 fake_saml_idp()->SetLoginAuthHTMLTemplate("saml_api_login_auth.html");
390 StartSamlAndWaitForIdpPageLoad(kUserEmail);
391
392 // Fill-in the SAML IdP form and submit.
393 SetSignFormField("Email", "fake_user");
394 SetSignFormField("Password", "fake_password");
395 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
396
397 // Login should finish login and a session should start.
398 content::WindowedNotificationObserver(
399 chrome::NOTIFICATION_SESSION_STARTED,
400 content::NotificationService::AllSources()).Wait();
401 }
402
403 // Tests the single password scraped flow.
404 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedSingle) {
405 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
406 StartSamlAndWaitForIdpPageLoad(kUserEmail);
407
408 // Fill-in the SAML IdP form and submit.
409 SetSignFormField("Email", "fake_user");
410 SetSignFormField("Password", "fake_password");
411 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
412
413 // Lands on confirm password screen.
414 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
415
416 // Enter an unknown password should go back to confirm password screen.
417 SendConfirmPassword("wrong_password");
418 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
419
420 // Enter a known password should finish login and start session.
421 SendConfirmPassword("fake_password");
422 content::WindowedNotificationObserver(
423 chrome::NOTIFICATION_SESSION_STARTED,
424 content::NotificationService::AllSources()).Wait();
425 }
426
427 // Tests the multiple password scraped flow.
428 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedMultiple) {
429 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_two_passwords.html");
430
431 StartSamlAndWaitForIdpPageLoad(kUserEmail);
432
433 SetSignFormField("Email", "fake_user");
434 SetSignFormField("Password", "fake_password");
435 SetSignFormField("Password1", "password1");
436 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
437
438 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
439
440 // Either scraped password should be able to sign-in.
441 SendConfirmPassword("password1");
442 content::WindowedNotificationObserver(
443 chrome::NOTIFICATION_SESSION_STARTED,
444 content::NotificationService::AllSources()).Wait();
445 }
446
447 // Tests the no password scraped flow.
448 IN_PROC_BROWSER_TEST_F(SamlTest, ScrapedNone) {
449 fake_saml_idp()->SetLoginHTMLTemplate("saml_login_no_passwords.html");
450
451 StartSamlAndWaitForIdpPageLoad(kUserEmail);
452
453 SetSignFormField("Email", "fake_user");
454 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
455
456 OobeScreenWaiter(OobeDisplay::SCREEN_MESSAGE_BOX).Wait();
457 JsExpect(
458 "$('message-box-title').textContent == "
459 "loadTimeData.getString('noPasswordWarningTitle')");
460 }
461
462 // Types |alice@example.com| into the GAIA login form but then authenticates as
463 // |bob@example.com| via SAML. Verifies that the logged-in user is correctly
464 // identified as Bob.
465 IN_PROC_BROWSER_TEST_F(SamlTest, UseAutenticatedUserEmailAddress) {
466 fake_saml_idp()->SetLoginHTMLTemplate("saml_login.html");
467 // Type |alice@example.com| into the GAIA login form.
468 StartSamlAndWaitForIdpPageLoad(kAnotherUserEmail);
469
470 // Authenticate as bob@example.com via SAML (the |Email| provided here is
471 // irrelevant - the authenticated user's e-mail address that FakeGAIA
472 // reports was set via SetMergeSessionParams()).
473 SetSignFormField("Email", "fake_user");
474 SetSignFormField("Password", "fake_password");
475 ExecuteJsInSigninFrame("document.getElementById('Submit').click();");
476
477 OobeScreenWaiter(OobeDisplay::SCREEN_CONFIRM_PASSWORD).Wait();
478
479 SendConfirmPassword("fake_password");
480 content::WindowedNotificationObserver(
481 chrome::NOTIFICATION_SESSION_STARTED,
482 content::NotificationService::AllSources()).Wait();
483 const User* user = UserManager::Get()->GetActiveUser();
484 ASSERT_TRUE(user);
485 EXPECT_EQ(kUserEmail, user->email());
486 }
487
488
489 } // namespace chromeos
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698