| // Copyright 2021 The Chromium Authors |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #import "chrome/browser/app_controller_mac.h" |
| |
| #include "base/apple/foundation_util.h" |
| #include "chrome/browser/browser_process.h" |
| #include "chrome/browser/profiles/profile_manager.h" |
| #include "chrome/browser/profiles/profile_test_util.h" |
| #include "chrome/browser/ui/browser.h" |
| #include "chrome/browser/ui/browser_list.h" |
| #include "chrome/browser/ui/browser_window.h" |
| #include "chrome/browser/ui/profile_picker.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/in_process_browser_test.h" |
| #include "chrome/test/base/ui_test_utils.h" |
| #include "components/prefs/pref_service.h" |
| #include "content/public/test/browser_test.h" |
| #include "net/base/mac/url_conversions.h" |
| #include "net/http/http_status_code.h" |
| #include "net/test/embedded_test_server/embedded_test_server.h" |
| #include "net/test/embedded_test_server/http_response.h" |
| #include "testing/gtest_mac.h" |
| #include "ui/base/page_transition_types.h" |
| |
| namespace { |
| |
| Profile& CreateAndWaitForProfile(const base::FilePath& profile_dir) { |
| Profile& profile = profiles::testing::CreateProfileSync( |
| g_browser_process->profile_manager(), profile_dir); |
| return profile; |
| } |
| |
| Profile& CreateAndWaitForGuestProfile() { |
| return CreateAndWaitForProfile(ProfileManager::GetGuestProfilePath()); |
| } |
| |
| void SetGuestProfileAsLastProfile() { |
| AppController* app_controller = AppController.sharedController; |
| // Create the guest profile, and set it as the last used profile. |
| Profile& guest_profile = CreateAndWaitForGuestProfile(); |
| [app_controller setLastProfile:&guest_profile]; |
| Profile* profile = [app_controller lastProfileIfLoaded]; |
| ASSERT_TRUE(profile); |
| EXPECT_EQ(guest_profile.GetPath(), profile->GetPath()); |
| EXPECT_TRUE(profile->IsGuestSession()); |
| // Also set the last used profile path preference. If the profile does need to |
| // be read from disk for some reason this acts as a backstop. |
| g_browser_process->local_state()->SetString( |
| prefs::kProfileLastUsed, guest_profile.GetPath().BaseName().value()); |
| } |
| |
| } // namespace |
| |
| using AuthSessionBrowserTest = InProcessBrowserTest; |
| |
| @interface MockASWebAuthenticationSessionRequest : NSObject { |
| NSUUID* __strong _uuid; |
| NSURL* __strong _initialURL; |
| |
| NSURL* __strong _callbackURL; |
| NSError* __strong _cancellationError; |
| } |
| |
| // ASWebAuthenticationSessionRequest: |
| |
| @property(readonly, nonatomic) NSURL* URL; |
| @property(readonly, nonatomic) BOOL shouldUseEphemeralSession; |
| @property(nullable, readonly, nonatomic, copy) NSString* callbackURLScheme; |
| @property(readonly, nonatomic) NSUUID* UUID; |
| |
| - (void)completeWithCallbackURL:(NSURL*)url; |
| - (void)cancelWithError:(NSError*)error; |
| |
| // Utilities: |
| |
| @property(readonly, nonatomic) NSURL* callbackURL; |
| @property(readonly, nonatomic) NSError* cancellationError; |
| |
| @end |
| |
| @implementation MockASWebAuthenticationSessionRequest |
| |
| - (instancetype)initWithInitialURL:(NSURL*)initialURL { |
| if (self = [super init]) { |
| _uuid = [[NSUUID alloc] init]; |
| _initialURL = initialURL; |
| } |
| return self; |
| } |
| |
| - (NSURL*)URL { |
| return _initialURL; |
| } |
| |
| - (BOOL)shouldUseEphemeralSession { |
| return false; |
| } |
| |
| - (NSString*)callbackURLScheme { |
| // Use occasional capital letters to test the canonicalization of schemes. |
| return @"mAkEiTsO"; |
| } |
| |
| - (NSUUID*)UUID { |
| return _uuid; |
| } |
| |
| - (void)completeWithCallbackURL:(NSURL*)url { |
| _callbackURL = url; |
| } |
| |
| - (void)cancelWithError:(NSError*)error { |
| _cancellationError = error; |
| } |
| |
| - (NSURL*)callbackURL { |
| return _callbackURL; |
| } |
| |
| - (NSError*)cancellationError { |
| return _cancellationError; |
| } |
| |
| @end |
| |
| // Tests that an OS request to cancel an auth session works. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, OSCancellation) { |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:[NSURL URLWithString:@"about:blank"]]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| |
| // Ask the app controller to start handling our session request. |
| |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect a browser window to be opened. |
| |
| Browser* browser = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_EQ(start_browser_count + 1, browser_list->size()); |
| |
| // Ask the app controller to stop handling our session request. |
| |
| [session_handler cancelWebAuthenticationSessionRequest:request]; |
| |
| // Expect the browser window to close. |
| |
| ui_test_utils::WaitForBrowserToClose(browser); |
| EXPECT_EQ(start_browser_count, browser_list->size()); |
| |
| // Expect there to have been the user cancellation callback. |
| |
| EXPECT_EQ(nil, session_request.callbackURL); |
| ASSERT_NE(nil, session_request.cancellationError); |
| EXPECT_EQ(ASWebAuthenticationSessionErrorDomain, |
| session_request.cancellationError.domain); |
| EXPECT_EQ(ASWebAuthenticationSessionErrorCodeCanceledLogin, |
| session_request.cancellationError.code); |
| } |
| |
| // Tests that a user request to cancel an auth session works. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, UserCancellation) { |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:[NSURL URLWithString:@"about:blank"]]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| |
| // Ask the app controller to start handling our session request. |
| |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect a browser window to be opened. |
| |
| Browser* browser = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_EQ(start_browser_count + 1, browser_list->size()); |
| |
| // Simulate the user closing the window. |
| |
| browser->window()->Close(); |
| |
| // Expect the browser window to close. |
| |
| ui_test_utils::WaitForBrowserToClose(browser); |
| EXPECT_EQ(start_browser_count, browser_list->size()); |
| |
| // Expect there to have been the user cancellation callback. |
| |
| EXPECT_EQ(nil, session_request.callbackURL); |
| ASSERT_NE(nil, session_request.cancellationError); |
| EXPECT_EQ(ASWebAuthenticationSessionErrorDomain, |
| session_request.cancellationError.domain); |
| EXPECT_EQ(ASWebAuthenticationSessionErrorCodeCanceledLogin, |
| session_request.cancellationError.code); |
| } |
| |
| // Tests that the session works even if the profile is not already loaded. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, ProfileNotLoaded) { |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| // Clear the last profile. It will be set by default since NSApp in browser |
| // tests can activate. |
| AppController* app_controller = AppController.sharedController; |
| [app_controller setLastProfile:nullptr]; |
| |
| // Use a profile that is not loaded yet. |
| const std::string kProfileName = "Profile 2"; |
| g_browser_process->local_state()->SetString(prefs::kProfileLastUsed, |
| kProfileName); |
| const base::FilePath kProfilePath = |
| browser()->profile()->GetPath().DirName().Append(kProfileName); |
| ASSERT_FALSE( |
| g_browser_process->profile_manager()->GetProfileByPath(kProfilePath)); |
| |
| // Ask the app controller to start handling our session request. |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:[NSURL URLWithString:@"about:blank"]]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect the profile to be loaded and browser window to be opened. |
| Browser* browser = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_TRUE( |
| g_browser_process->profile_manager()->GetProfileByPath(kProfilePath)); |
| EXPECT_EQ(start_browser_count + 1, browser_list->size()); |
| EXPECT_EQ(browser->profile()->GetPath(), kProfilePath); |
| } |
| |
| // Tests that the profile picker is shown instead if the profile is unavailable. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, ProfileNotAvailable) { |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| // Use the guest profile, but mark it as disallowed. |
| SetGuestProfileAsLastProfile(); |
| PrefService* local_state = g_browser_process->local_state(); |
| local_state->SetBoolean(prefs::kBrowserGuestModeEnabled, false); |
| |
| // The profile picker is initially closed. |
| base::RunLoop run_loop; |
| ProfilePicker::AddOnProfilePickerOpenedCallbackForTesting( |
| run_loop.QuitClosure()); |
| ASSERT_FALSE(ProfilePicker::IsOpen()); |
| |
| // Ask the app controller to start handling our session request. |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:[NSURL URLWithString:@"about:blank"]]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect the profile picker to be opened, no browser was created, and the |
| // session was cancelled. |
| run_loop.Run(); |
| EXPECT_TRUE(ProfilePicker::IsOpen()); |
| EXPECT_EQ(start_browser_count, browser_list->size()); |
| EXPECT_EQ(nil, session_request.callbackURL); |
| ASSERT_NE(nil, session_request.cancellationError); |
| EXPECT_EQ(ASWebAuthenticationSessionErrorDomain, |
| session_request.cancellationError.domain); |
| EXPECT_EQ(ASWebAuthenticationSessionErrorCodePresentationContextInvalid, |
| session_request.cancellationError.code); |
| } |
| |
| // Tests that a successful auth session works via direct navigation. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, UserSuccessDirect) { |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:[NSURL URLWithString:@"about:blank"]]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| |
| // Ask the app controller to start handling our session request. |
| |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect a browser window to be opened. |
| |
| Browser* browser = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_EQ(start_browser_count + 1, browser_list->size()); |
| |
| // Simulate the user successfully logging in with a non-redirected load of |
| // a URL with the expected scheme. |
| |
| GURL success_url("makeitso://enterprise"); |
| browser->tab_strip_model()->GetWebContentsAt(0)->GetController().LoadURL( |
| success_url, content::Referrer(), ui::PAGE_TRANSITION_GENERATED, |
| std::string()); |
| |
| // Expect the browser window to close. |
| |
| ui_test_utils::WaitForBrowserToClose(browser); |
| EXPECT_EQ(start_browser_count, browser_list->size()); |
| |
| // Expect there to have been the success callback. |
| |
| ASSERT_NE(nil, session_request.callbackURL); |
| EXPECT_EQ(nil, session_request.cancellationError); |
| EXPECT_NSEQ(net::NSURLWithGURL(success_url), session_request.callbackURL); |
| } |
| |
| namespace { |
| |
| std::unique_ptr<net::test_server::HttpResponse> RedirectionRequestHandler( |
| const GURL& redirection_url, |
| const net::test_server::HttpRequest& request) { |
| auto http_response = std::make_unique<net::test_server::BasicHttpResponse>(); |
| http_response->set_code(net::HTTP_FOUND); |
| http_response->AddCustomHeader("Location", redirection_url.spec()); |
| return http_response; |
| } |
| |
| } // namespace |
| |
| // Tests that a successful auth session works via a redirect. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, UserSuccessEventualRedirect) { |
| GURL success_url("makeitso://cerritos"); |
| |
| net::EmbeddedTestServer embedded_test_server; |
| embedded_test_server.RegisterRequestHandler( |
| base::BindRepeating(RedirectionRequestHandler, success_url)); |
| ASSERT_TRUE(embedded_test_server.Start()); |
| |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:[NSURL URLWithString:@"about:blank"]]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| |
| // Ask the app controller to start handling our session request. |
| |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect a browser window to be opened. |
| |
| Browser* browser = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_EQ(start_browser_count + 1, browser_list->size()); |
| |
| // Simulate the user successfully logging in with a redirected load of a URL |
| // with the expected scheme. |
| |
| GURL url = embedded_test_server.GetURL("/something"); |
| browser->tab_strip_model()->GetWebContentsAt(0)->GetController().LoadURL( |
| url, content::Referrer(), ui::PAGE_TRANSITION_GENERATED, std::string()); |
| |
| // Expect the browser window to close. |
| |
| ui_test_utils::WaitForBrowserToClose(browser); |
| EXPECT_EQ(start_browser_count, browser_list->size()); |
| |
| // Expect there to have been the success callback. |
| |
| ASSERT_NE(nil, session_request.callbackURL); |
| EXPECT_EQ(nil, session_request.cancellationError); |
| EXPECT_NSEQ(net::NSURLWithGURL(success_url), session_request.callbackURL); |
| } |
| |
| // Tests that a successful auth session works if the success scheme comes on a |
| // redirect from the initial navigation. |
| IN_PROC_BROWSER_TEST_F(AuthSessionBrowserTest, UserSuccessInitialRedirect) { |
| GURL success_url("makeitso://titan"); |
| |
| net::EmbeddedTestServer embedded_test_server; |
| embedded_test_server.RegisterRequestHandler( |
| base::BindRepeating(RedirectionRequestHandler, success_url)); |
| ASSERT_TRUE(embedded_test_server.Start()); |
| |
| auto* browser_list = BrowserList::GetInstance(); |
| size_t start_browser_count = browser_list->size(); |
| |
| GURL url = embedded_test_server.GetURL("/something"); |
| MockASWebAuthenticationSessionRequest* session_request = |
| [[MockASWebAuthenticationSessionRequest alloc] |
| initWithInitialURL:net::NSURLWithGURL(url)]; |
| id<ASWebAuthenticationSessionWebBrowserSessionHandling> session_handler = |
| ASWebAuthenticationSessionWebBrowserSessionManager.sharedManager |
| .sessionHandler; |
| ASSERT_NE(nil, session_handler); |
| |
| // Ask the app controller to start handling our session request. |
| |
| id request = session_request; |
| [session_handler beginHandlingWebAuthenticationSessionRequest:request]; |
| |
| // Expect a browser window to be opened. |
| |
| Browser* browser = ui_test_utils::WaitForBrowserToOpen(); |
| EXPECT_EQ(start_browser_count + 1, browser_list->size()); |
| |
| // Expect the browser window to close. |
| |
| ui_test_utils::WaitForBrowserToClose(browser); |
| EXPECT_EQ(start_browser_count, browser_list->size()); |
| |
| // Expect there to have been the success callback. |
| |
| ASSERT_NE(nil, session_request.callbackURL); |
| EXPECT_EQ(nil, session_request.cancellationError); |
| EXPECT_NSEQ(net::NSURLWithGURL(success_url), session_request.callbackURL); |
| } |