| // Copyright 2017 The Chromium Authors. All rights reserved. |
| // Use of this source code is governed by a BSD-style license that can be |
| // found in the LICENSE file. |
| |
| #include "chrome/browser/ash/smb_client/smb_service.h" |
| |
| #include <stddef.h> |
| |
| #include <memory> |
| #include <utility> |
| |
| #include "ash/components/disks/disk_mount_manager.h" |
| #include "ash/components/smbfs/smbfs_host.h" |
| #include "ash/components/smbfs/smbfs_mounter.h" |
| #include "base/bind.h" |
| #include "base/callback_helpers.h" |
| #include "base/files/file_util.h" |
| #include "base/ignore_result.h" |
| #include "base/json/json_reader.h" |
| #include "base/memory/ptr_util.h" |
| #include "base/run_loop.h" |
| #include "base/strings/strcat.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/strings/string_util.h" |
| #include "base/test/bind.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "base/test/scoped_feature_list.h" |
| #include "base/test/simple_test_tick_clock.h" |
| #include "base/threading/thread_task_runner_handle.h" |
| #include "chrome/browser/ash/file_manager/fake_disk_mount_manager.h" |
| #include "chrome/browser/ash/file_manager/volume_manager.h" |
| #include "chrome/browser/ash/file_manager/volume_manager_factory.h" |
| #include "chrome/browser/ash/file_system_provider/fake_provided_file_system.h" |
| #include "chrome/browser/ash/file_system_provider/fake_registry.h" |
| #include "chrome/browser/ash/file_system_provider/icon_set.h" |
| #include "chrome/browser/ash/file_system_provider/provided_file_system_info.h" |
| #include "chrome/browser/ash/file_system_provider/service.h" |
| #include "chrome/browser/ash/login/users/fake_chrome_user_manager.h" |
| #include "chrome/browser/ash/smb_client/smb_file_system_id.h" |
| #include "chrome/browser/ash/smb_client/smb_persisted_share_registry.h" |
| #include "chrome/common/chrome_features.h" |
| #include "chrome/common/pref_names.h" |
| #include "chrome/test/base/testing_browser_process.h" |
| #include "chrome/test/base/testing_profile.h" |
| #include "chrome/test/base/testing_profile_manager.h" |
| #include "chromeos/dbus/concierge/concierge_client.h" |
| #include "chromeos/dbus/dbus_thread_manager.h" |
| #include "chromeos/dbus/smbprovider/fake_smb_provider_client.h" |
| #include "components/user_manager/scoped_user_manager.h" |
| #include "content/public/test/browser_task_environment.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "testing/gmock/include/gmock/gmock.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| using ::testing::_; |
| using ::testing::AllOf; |
| using ::testing::Field; |
| using ::testing::Invoke; |
| using ::testing::Ne; |
| using ::testing::NiceMock; |
| using ::testing::WithArg; |
| using ::testing::WithArgs; |
| |
| namespace ash { |
| namespace smb_client { |
| |
| namespace { |
| |
| constexpr char kTestUser[] = "foobar"; |
| constexpr char kTestPassword[] = "my_secret_password"; |
| constexpr char kTestDomain[] = "EXAMPLE.COM"; |
| constexpr char kSharePath[] = "\\\\server\\foobar"; |
| constexpr char kSharePath2[] = "\\\\server2\\second_share"; |
| constexpr char kShareUrl[] = "smb://server/foobar"; |
| constexpr char kDisplayName[] = "My Share"; |
| constexpr char kMountPath[] = "/share/mount/path"; |
| constexpr char kMountPath2[] = "/share/mount/second_path"; |
| |
| constexpr char kTestADUser[] = "ad-test-user"; |
| constexpr char kTestADDomain[] = "foorbar.corp"; |
| constexpr char kTestADGuid[] = "ad-user-guid"; |
| |
| void SaveMountResult(SmbMountResult* out, SmbMountResult result) { |
| *out = result; |
| } |
| |
| class MockSmbFsMounter : public smbfs::SmbFsMounter { |
| public: |
| MOCK_METHOD(void, |
| Mount, |
| (smbfs::SmbFsMounter::DoneCallback callback), |
| (override)); |
| }; |
| |
| class MockSmbFsImpl : public smbfs::mojom::SmbFs { |
| public: |
| explicit MockSmbFsImpl(mojo::PendingReceiver<smbfs::mojom::SmbFs> pending) |
| : receiver_(this, std::move(pending)) { |
| receiver_.set_disconnect_handler( |
| base::BindOnce(&MockSmbFsImpl::OnDisconnect, base::Unretained(this))); |
| } |
| |
| // Mojo disconnection handler. |
| MOCK_METHOD(void, OnDisconnect, (), ()); |
| |
| // smbfs::mojom::SmbFs overrides. |
| MOCK_METHOD(void, |
| RemoveSavedCredentials, |
| (RemoveSavedCredentialsCallback), |
| (override)); |
| |
| MOCK_METHOD(void, |
| DeleteRecursively, |
| (const base::FilePath&, DeleteRecursivelyCallback), |
| (override)); |
| |
| private: |
| mojo::Receiver<smbfs::mojom::SmbFs> receiver_; |
| }; |
| |
| // Creates a new VolumeManager for tests. |
| // By default, VolumeManager KeyedService is null for testing. |
| std::unique_ptr<KeyedService> BuildVolumeManager( |
| content::BrowserContext* context) { |
| return std::make_unique<file_manager::VolumeManager>( |
| Profile::FromBrowserContext(context), |
| nullptr /* drive_integration_service */, |
| nullptr /* power_manager_client */, |
| disks::DiskMountManager::GetInstance(), |
| nullptr /* file_system_provider_service */, |
| file_manager::VolumeManager::GetMtpStorageInfoCallback()); |
| } |
| |
| } // namespace |
| |
| class SmbServiceWithSmbfsTest : public testing::Test { |
| protected: |
| // Mojo endpoints owned by the smbfs instance. |
| struct TestSmbFsInstance { |
| explicit TestSmbFsInstance( |
| mojo::PendingReceiver<smbfs::mojom::SmbFs> pending) |
| : mock_smbfs(std::move(pending)) {} |
| |
| MockSmbFsImpl mock_smbfs; |
| mojo::Remote<smbfs::mojom::SmbFsDelegate> delegate; |
| }; |
| |
| SmbServiceWithSmbfsTest() { |
| profile_manager_ = std::make_unique<TestingProfileManager>( |
| TestingBrowserProcess::GetGlobal()); |
| EXPECT_TRUE(profile_manager_->SetUp()); |
| |
| std::unique_ptr<FakeChromeUserManager> user_manager_temp = |
| std::make_unique<FakeChromeUserManager>(); |
| |
| profile_ = profile_manager_->CreateTestingProfile("test-user@example.com"); |
| user_manager_temp->AddUser( |
| AccountId::FromUserEmail(profile_->GetProfileUserName())); |
| |
| ad_profile_ = profile_manager_->CreateTestingProfile( |
| base::StrCat({kTestADUser, "@", kTestADDomain})); |
| user_manager_temp->AddUserWithAffiliationAndTypeAndProfile( |
| AccountId::AdFromUserEmailObjGuid(ad_profile_->GetProfileUserName(), |
| kTestADGuid), |
| false, user_manager::UserType::USER_TYPE_ACTIVE_DIRECTORY, ad_profile_); |
| |
| // Run pending async tasks resulting from profile construction to ensure |
| // these are complete before the test begins. |
| base::RunLoop().RunUntilIdle(); |
| |
| user_manager_enabler_ = std::make_unique<user_manager::ScopedUserManager>( |
| std::move(user_manager_temp)); |
| |
| // This isn't used, but still needs to exist. |
| chromeos::DBusThreadManager::Initialize(); |
| chromeos::DBusThreadManager::GetSetterForTesting()->SetSmbProviderClient( |
| std::make_unique<FakeSmbProviderClient>()); |
| chromeos::ConciergeClient::InitializeFake(/*fake_cicerone_client=*/nullptr); |
| |
| // Takes ownership of |disk_mount_manager_|, but Shutdown() must be called. |
| disks::DiskMountManager::InitializeForTesting(disk_mount_manager_); |
| } |
| |
| ~SmbServiceWithSmbfsTest() override { |
| smb_service_.reset(); |
| user_manager_enabler_.reset(); |
| profile_manager_.reset(); |
| disks::DiskMountManager::Shutdown(); |
| chromeos::ConciergeClient::Shutdown(); |
| chromeos::DBusThreadManager::Shutdown(); |
| } |
| |
| void CreateService(TestingProfile* profile) { |
| SmbService::DisableShareDiscoveryForTesting(); |
| file_manager::VolumeManagerFactory::GetInstance()->SetTestingFactory( |
| profile, base::BindRepeating(&BuildVolumeManager)); |
| |
| // Create smb service. |
| smb_service_ = std::make_unique<SmbService>( |
| profile, std::make_unique<base::SimpleTestTickClock>()); |
| } |
| |
| void ExpectInvalidUrl(const std::string& url) { |
| SmbMountResult result = SmbMountResult::kSuccess; |
| smb_service_->Mount("" /* display_name */, base::FilePath(url), |
| "" /* username */, "" /* password */, |
| false /* use_chromad_kerberos */, |
| false /* should_open_file_manager_after_mount */, |
| false /* save_credentials */, |
| base::BindOnce(&SaveMountResult, &result)); |
| EXPECT_EQ(result, SmbMountResult::kInvalidUrl); |
| } |
| |
| void ExpectInvalidSsoUrl(const std::string& url) { |
| SmbMountResult result = SmbMountResult::kSuccess; |
| smb_service_->Mount("" /* display_name */, base::FilePath(url), |
| "" /* username */, "" /* password */, |
| true /* use_chromad_kerberos */, |
| false /* should_open_file_manager_after_mount */, |
| false /* save_credentials */, |
| base::BindOnce(&SaveMountResult, &result)); |
| EXPECT_EQ(result, SmbMountResult::kInvalidSsoUrl); |
| } |
| |
| void WaitForSetupComplete() { |
| { |
| base::RunLoop run_loop; |
| smb_service_->OnSetupCompleteForTesting(run_loop.QuitClosure()); |
| run_loop.Run(); |
| } |
| { |
| // Share gathering needs to complete at least once before a share can be |
| // mounted. |
| base::RunLoop run_loop; |
| smb_service_->GatherSharesInNetwork( |
| base::DoNothing(), |
| base::BindLambdaForTesting( |
| [&run_loop](const std::vector<SmbUrl>& shares_gathered, |
| bool done) { |
| if (done) { |
| run_loop.Quit(); |
| } |
| })); |
| run_loop.Run(); |
| } |
| } |
| |
| std::unique_ptr<disks::MountPoint> MakeMountPoint( |
| const base::FilePath& path) { |
| return std::make_unique<disks::MountPoint>(path, disk_mount_manager_); |
| } |
| |
| // Helper function for creating a basic smbfs mount with an empty |
| // username/password. |
| std::unique_ptr<TestSmbFsInstance> MountBasicShare( |
| const std::string& share_path, |
| const std::string& mount_path, |
| SmbService::MountResponse callback) { |
| mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote; |
| std::unique_ptr<TestSmbFsInstance> instance = |
| std::make_unique<TestSmbFsInstance>( |
| smbfs_remote.BindNewPipeAndPassReceiver()); |
| |
| smbfs::SmbFsHost::Delegate* smbfs_host_delegate = nullptr; |
| // Use a NiceMock<> so that the ON_CALL below doesn't complain. |
| std::unique_ptr<MockSmbFsMounter> mock_mounter = |
| std::make_unique<NiceMock<MockSmbFsMounter>>(); |
| |
| smb_service_->SetSmbFsMounterCreationCallbackForTesting( |
| base::BindLambdaForTesting([&mock_mounter, &smbfs_host_delegate]( |
| const std::string& share_path, |
| const std::string& mount_dir_name, |
| const SmbFsShare::MountOptions& options, |
| smbfs::SmbFsHost::Delegate* delegate) |
| -> std::unique_ptr<smbfs::SmbFsMounter> { |
| smbfs_host_delegate = delegate; |
| return std::move(mock_mounter); |
| })); |
| |
| // Use ON_CALL instead of EXPECT_CALL because there might be a failure |
| // earlier in the mount process and this won't be called. |
| ON_CALL(*mock_mounter, Mount(_)) |
| .WillByDefault( |
| [this, &smbfs_host_delegate, &smbfs_remote, &instance, |
| &mount_path](smbfs::SmbFsMounter::DoneCallback mount_callback) { |
| std::move(mount_callback) |
| .Run(smbfs::mojom::MountError::kOk, |
| std::make_unique<smbfs::SmbFsHost>( |
| MakeMountPoint(base::FilePath(mount_path)), |
| smbfs_host_delegate, std::move(smbfs_remote), |
| instance->delegate.BindNewPipeAndPassReceiver())); |
| }); |
| |
| base::RunLoop run_loop; |
| smb_service_->Mount(kDisplayName, base::FilePath(share_path), |
| "" /* username */, "" /* password */, |
| false /* use_chromad_kerberos */, |
| false /* should_open_file_manager_after_mount */, |
| false /* save_credentials */, |
| base::BindLambdaForTesting( |
| [&run_loop, &callback](SmbMountResult result) { |
| std::move(callback).Run(result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| return instance; |
| } |
| |
| content::BrowserTaskEnvironment task_environment_{ |
| content::BrowserTaskEnvironment::REAL_IO_THREAD}; |
| base::test::ScopedFeatureList scoped_feature_list_; |
| file_manager::FakeDiskMountManager* disk_mount_manager_ = |
| new file_manager::FakeDiskMountManager; |
| |
| TestingProfile* profile_ = nullptr; // Not owned. |
| TestingProfile* ad_profile_ = nullptr; // Not owned. |
| std::unique_ptr<TestingProfileManager> profile_manager_; |
| std::unique_ptr<user_manager::ScopedUserManager> user_manager_enabler_; |
| std::unique_ptr<SmbService> smb_service_; |
| }; |
| |
| TEST_F(SmbServiceWithSmbfsTest, InvalidUrls) { |
| CreateService(profile_); |
| |
| ExpectInvalidUrl(""); |
| ExpectInvalidUrl("foo"); |
| ExpectInvalidUrl("\\foo"); |
| ExpectInvalidUrl("\\\\foo"); |
| ExpectInvalidUrl("\\\\foo\\"); |
| ExpectInvalidUrl("file://foo/bar"); |
| ExpectInvalidUrl("smb://foo"); |
| ExpectInvalidUrl("smb://user@password:foo"); |
| ExpectInvalidUrl("smb:\\\\foo\\bar"); |
| ExpectInvalidUrl("//foo/bar"); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, InvalidSsoUrls) { |
| CreateService(profile_); |
| |
| ExpectInvalidSsoUrl("\\\\192.168.1.1\\foo"); |
| ExpectInvalidSsoUrl("\\\\[0:0:0:0:0:0:0:1]\\foo"); |
| ExpectInvalidSsoUrl("\\\\[::1]\\foo"); |
| ExpectInvalidSsoUrl("smb://192.168.1.1/foo"); |
| ExpectInvalidSsoUrl("smb://[0:0:0:0:0:0:0:1]/foo"); |
| ExpectInvalidSsoUrl("smb://[::1]/foo"); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, Mount) { |
| CreateService(profile_); |
| WaitForSetupComplete(); |
| |
| mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote; |
| MockSmbFsImpl smbfs_impl(smbfs_remote.BindNewPipeAndPassReceiver()); |
| mojo::Remote<smbfs::mojom::SmbFsDelegate> smbfs_delegate_remote; |
| |
| smbfs::SmbFsHost::Delegate* smbfs_host_delegate = nullptr; |
| std::unique_ptr<MockSmbFsMounter> mock_mounter = |
| std::make_unique<MockSmbFsMounter>(); |
| smb_service_->SetSmbFsMounterCreationCallbackForTesting( |
| base::BindLambdaForTesting([&mock_mounter, &smbfs_host_delegate]( |
| const std::string& share_path, |
| const std::string& mount_dir_name, |
| const SmbFsShare::MountOptions& options, |
| smbfs::SmbFsHost::Delegate* delegate) |
| -> std::unique_ptr<smbfs::SmbFsMounter> { |
| EXPECT_EQ(share_path, kShareUrl); |
| EXPECT_EQ(options.username, kTestUser); |
| EXPECT_TRUE(options.workgroup.empty()); |
| EXPECT_EQ(options.password, kTestPassword); |
| EXPECT_EQ(options.allow_ntlm, true); |
| EXPECT_FALSE(options.kerberos_options); |
| smbfs_host_delegate = delegate; |
| return std::move(mock_mounter); |
| })); |
| EXPECT_CALL(*mock_mounter, Mount(_)) |
| .WillOnce( |
| [this, &smbfs_host_delegate, &smbfs_remote, |
| &smbfs_delegate_remote](smbfs::SmbFsMounter::DoneCallback callback) { |
| std::move(callback).Run( |
| smbfs::mojom::MountError::kOk, |
| std::make_unique<smbfs::SmbFsHost>( |
| MakeMountPoint(base::FilePath(kMountPath)), |
| smbfs_host_delegate, std::move(smbfs_remote), |
| smbfs_delegate_remote.BindNewPipeAndPassReceiver())); |
| }); |
| |
| base::RunLoop run_loop; |
| smb_service_->Mount( |
| kDisplayName, base::FilePath(kSharePath), kTestUser, kTestPassword, |
| false /* use_chromad_kerberos */, |
| false /* should_open_file_manager_after_mount */, |
| false /* save_credentials */, |
| base::BindLambdaForTesting([&run_loop](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| // Expect that the filesystem mount path is registered. |
| std::vector<storage::MountPoints::MountPointInfo> mount_points; |
| storage::ExternalMountPoints::GetSystemInstance()->AddMountPointInfosTo( |
| &mount_points); |
| bool found = false; |
| for (const auto& info : mount_points) { |
| if (info.path == base::FilePath(kMountPath)) { |
| found = true; |
| break; |
| } |
| } |
| EXPECT_TRUE(found); |
| |
| // Check that the SmbFsShare can be accessed. |
| const base::FilePath mount_path(kMountPath); |
| SmbFsShare* share = smb_service_->GetSmbFsShareForPath(mount_path); |
| ASSERT_TRUE(share); |
| EXPECT_EQ(share->mount_path(), mount_path); |
| EXPECT_EQ(share->share_url().ToString(), kShareUrl); |
| |
| // Check that the share was saved. |
| SmbPersistedShareRegistry registry(profile_); |
| absl::optional<SmbShareInfo> info = registry.Get(SmbUrl(kShareUrl)); |
| ASSERT_TRUE(info); |
| EXPECT_EQ(info->share_url().ToString(), kShareUrl); |
| EXPECT_EQ(info->display_name(), kDisplayName); |
| EXPECT_EQ(info->username(), kTestUser); |
| EXPECT_TRUE(info->workgroup().empty()); |
| EXPECT_FALSE(info->use_kerberos()); |
| |
| // Unmounting should remove the saved share. Since |save_credentials| was |
| // false, there should be no request to smbfs. |
| EXPECT_CALL(smbfs_impl, RemoveSavedCredentials(_)).Times(0); |
| smb_service_->UnmountSmbFs(base::FilePath(kMountPath)); |
| info = registry.Get(SmbUrl(kShareUrl)); |
| EXPECT_FALSE(info); |
| EXPECT_TRUE(registry.GetAll().empty()); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, Mount_SaveCredentials) { |
| CreateService(profile_); |
| WaitForSetupComplete(); |
| |
| mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote; |
| MockSmbFsImpl smbfs_impl(smbfs_remote.BindNewPipeAndPassReceiver()); |
| mojo::Remote<smbfs::mojom::SmbFsDelegate> smbfs_delegate_remote; |
| |
| smbfs::SmbFsHost::Delegate* smbfs_host_delegate = nullptr; |
| std::unique_ptr<MockSmbFsMounter> mock_mounter = |
| std::make_unique<MockSmbFsMounter>(); |
| smb_service_->SetSmbFsMounterCreationCallbackForTesting( |
| base::BindLambdaForTesting([&mock_mounter, &smbfs_host_delegate]( |
| const std::string& share_path, |
| const std::string& mount_dir_name, |
| const SmbFsShare::MountOptions& options, |
| smbfs::SmbFsHost::Delegate* delegate) |
| -> std::unique_ptr<smbfs::SmbFsMounter> { |
| EXPECT_EQ(share_path, kShareUrl); |
| EXPECT_EQ(options.username, kTestUser); |
| EXPECT_TRUE(options.workgroup.empty()); |
| EXPECT_EQ(options.password, kTestPassword); |
| EXPECT_FALSE(options.kerberos_options); |
| EXPECT_TRUE(options.save_restore_password); |
| EXPECT_FALSE(options.account_hash.empty()); |
| EXPECT_FALSE(options.password_salt.empty()); |
| smbfs_host_delegate = delegate; |
| return std::move(mock_mounter); |
| })); |
| EXPECT_CALL(*mock_mounter, Mount(_)) |
| .WillOnce( |
| [this, &smbfs_host_delegate, &smbfs_remote, |
| &smbfs_delegate_remote](smbfs::SmbFsMounter::DoneCallback callback) { |
| std::move(callback).Run( |
| smbfs::mojom::MountError::kOk, |
| std::make_unique<smbfs::SmbFsHost>( |
| MakeMountPoint(base::FilePath(kMountPath)), |
| smbfs_host_delegate, std::move(smbfs_remote), |
| smbfs_delegate_remote.BindNewPipeAndPassReceiver())); |
| }); |
| |
| base::RunLoop run_loop; |
| smb_service_->Mount( |
| kDisplayName, base::FilePath(kSharePath), kTestUser, kTestPassword, |
| false /* use_chromad_kerberos */, |
| false /* should_open_file_manager_after_mount */, |
| true /* save_credentials */, |
| base::BindLambdaForTesting([&run_loop](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| // Check that the share was saved. |
| SmbPersistedShareRegistry registry(profile_); |
| absl::optional<SmbShareInfo> info = registry.Get(SmbUrl(kShareUrl)); |
| ASSERT_TRUE(info); |
| EXPECT_EQ(info->share_url().ToString(), kShareUrl); |
| EXPECT_EQ(info->display_name(), kDisplayName); |
| EXPECT_EQ(info->username(), kTestUser); |
| EXPECT_TRUE(info->workgroup().empty()); |
| EXPECT_FALSE(info->use_kerberos()); |
| EXPECT_FALSE(info->password_salt().empty()); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, Mount_ActiveDirectory) { |
| CreateService(ad_profile_); |
| WaitForSetupComplete(); |
| |
| mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote; |
| MockSmbFsImpl smbfs_impl(smbfs_remote.BindNewPipeAndPassReceiver()); |
| mojo::Remote<smbfs::mojom::SmbFsDelegate> smbfs_delegate_remote; |
| |
| smbfs::SmbFsHost::Delegate* smbfs_host_delegate = nullptr; |
| std::unique_ptr<MockSmbFsMounter> mock_mounter = |
| std::make_unique<MockSmbFsMounter>(); |
| smb_service_->SetSmbFsMounterCreationCallbackForTesting( |
| base::BindLambdaForTesting([&mock_mounter, &smbfs_host_delegate]( |
| const std::string& share_path, |
| const std::string& mount_dir_name, |
| const SmbFsShare::MountOptions& options, |
| smbfs::SmbFsHost::Delegate* delegate) |
| -> std::unique_ptr<smbfs::SmbFsMounter> { |
| EXPECT_EQ(share_path, kShareUrl); |
| // Username/workgroup/password are ignored when using Active |
| // Directory. Username/workgroup are derived from the account email |
| // address. Password is unused and dropped. |
| EXPECT_EQ(options.username, kTestADUser); |
| // Workgroup/domain is converted to upper-case. |
| EXPECT_EQ(options.workgroup, base::ToUpperASCII(kTestADDomain)); |
| EXPECT_TRUE(options.password.empty()); |
| EXPECT_EQ(options.allow_ntlm, true); |
| EXPECT_EQ( |
| options.kerberos_options->source, |
| smbfs::SmbFsMounter::KerberosOptions::Source::kActiveDirectory); |
| EXPECT_EQ(options.kerberos_options->identity, kTestADGuid); |
| smbfs_host_delegate = delegate; |
| return std::move(mock_mounter); |
| })); |
| EXPECT_CALL(*mock_mounter, Mount(_)) |
| .WillOnce( |
| [this, &smbfs_host_delegate, &smbfs_remote, |
| &smbfs_delegate_remote](smbfs::SmbFsMounter::DoneCallback callback) { |
| std::move(callback).Run( |
| smbfs::mojom::MountError::kOk, |
| std::make_unique<smbfs::SmbFsHost>( |
| MakeMountPoint(base::FilePath(kMountPath)), |
| smbfs_host_delegate, std::move(smbfs_remote), |
| smbfs_delegate_remote.BindNewPipeAndPassReceiver())); |
| }); |
| |
| base::RunLoop run_loop; |
| smb_service_->Mount( |
| kDisplayName, base::FilePath(kSharePath), |
| base::StrCat({kTestUser, "@", kTestDomain}), kTestPassword, |
| true /* use_chromad_kerberos */, |
| false /* should_open_file_manager_after_mount */, |
| false /* save_credentials */, |
| base::BindLambdaForTesting([&run_loop](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| run_loop.Quit(); |
| })); |
| run_loop.Run(); |
| |
| // Check that the share was saved. |
| SmbPersistedShareRegistry registry(ad_profile_); |
| absl::optional<SmbShareInfo> info = registry.Get(SmbUrl(kShareUrl)); |
| ASSERT_TRUE(info); |
| EXPECT_EQ(info->share_url().ToString(), kShareUrl); |
| EXPECT_EQ(info->display_name(), kDisplayName); |
| EXPECT_EQ(info->username(), kTestADUser); |
| // Workgroup/domain is converted to upper-case. |
| EXPECT_EQ(info->workgroup(), base::ToUpperASCII(kTestADDomain)); |
| EXPECT_TRUE(info->use_kerberos()); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, PreconfiguredMount) { |
| const char kPremountPath[] = "smb://preconfigured/share"; |
| const char kPreconfiguredShares[] = |
| R"([{"mode":"pre_mount","share_url":"\\\\preconfigured\\share"}])"; |
| auto parsed_shares = base::JSONReader::Read(kPreconfiguredShares); |
| ASSERT_TRUE(parsed_shares); |
| profile_->GetPrefs()->Set(prefs::kNetworkFileSharesPreconfiguredShares, |
| *parsed_shares); |
| |
| CreateService(profile_); |
| |
| mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote; |
| MockSmbFsImpl smbfs_impl(smbfs_remote.BindNewPipeAndPassReceiver()); |
| mojo::Remote<smbfs::mojom::SmbFsDelegate> smbfs_delegate_remote; |
| |
| smbfs::SmbFsHost::Delegate* smbfs_host_delegate = nullptr; |
| std::unique_ptr<MockSmbFsMounter> mock_mounter = |
| std::make_unique<MockSmbFsMounter>(); |
| smb_service_->SetSmbFsMounterCreationCallbackForTesting( |
| base::BindLambdaForTesting( |
| [&mock_mounter, &smbfs_host_delegate, kPremountPath]( |
| const std::string& share_path, const std::string& mount_dir_name, |
| const SmbFsShare::MountOptions& options, |
| smbfs::SmbFsHost::Delegate* delegate) |
| -> std::unique_ptr<smbfs::SmbFsMounter> { |
| EXPECT_EQ(share_path, kPremountPath); |
| EXPECT_TRUE(options.username.empty()); |
| EXPECT_TRUE(options.workgroup.empty()); |
| EXPECT_TRUE(options.password.empty()); |
| EXPECT_FALSE(options.kerberos_options); |
| smbfs_host_delegate = delegate; |
| return std::move(mock_mounter); |
| })); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_mounter, Mount(_)) |
| .WillOnce([this, &smbfs_host_delegate, &smbfs_remote, |
| &smbfs_delegate_remote, |
| &run_loop](smbfs::SmbFsMounter::DoneCallback callback) { |
| std::move(callback).Run( |
| smbfs::mojom::MountError::kOk, |
| std::make_unique<smbfs::SmbFsHost>( |
| MakeMountPoint(base::FilePath(kMountPath)), smbfs_host_delegate, |
| std::move(smbfs_remote), |
| smbfs_delegate_remote.BindNewPipeAndPassReceiver())); |
| run_loop.Quit(); |
| }); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, MountSaved) { |
| const std::vector<uint8_t> kSalt = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| // Save share in profile. |
| { |
| SmbPersistedShareRegistry registry(profile_); |
| SmbShareInfo info(SmbUrl(kShareUrl), kDisplayName, kTestUser, kTestDomain, |
| false /* use_kerberos */, kSalt); |
| registry.Save(info); |
| } |
| |
| CreateService(profile_); |
| |
| mojo::Remote<smbfs::mojom::SmbFs> smbfs_remote; |
| MockSmbFsImpl smbfs_impl(smbfs_remote.BindNewPipeAndPassReceiver()); |
| mojo::Remote<smbfs::mojom::SmbFsDelegate> smbfs_delegate_remote; |
| |
| smbfs::SmbFsHost::Delegate* smbfs_host_delegate = nullptr; |
| std::unique_ptr<MockSmbFsMounter> mock_mounter = |
| std::make_unique<MockSmbFsMounter>(); |
| smb_service_->SetSmbFsMounterCreationCallbackForTesting( |
| base::BindLambdaForTesting([&mock_mounter, &smbfs_host_delegate, kSalt]( |
| const std::string& share_path, |
| const std::string& mount_dir_name, |
| const SmbFsShare::MountOptions& options, |
| smbfs::SmbFsHost::Delegate* delegate) |
| -> std::unique_ptr<smbfs::SmbFsMounter> { |
| EXPECT_EQ(share_path, kShareUrl); |
| EXPECT_EQ(options.username, kTestUser); |
| EXPECT_EQ(options.workgroup, kTestDomain); |
| EXPECT_TRUE(options.password.empty()); |
| EXPECT_EQ(options.allow_ntlm, true); |
| EXPECT_FALSE(options.kerberos_options); |
| EXPECT_TRUE(options.save_restore_password); |
| EXPECT_FALSE(options.account_hash.empty()); |
| EXPECT_EQ(options.password_salt, kSalt); |
| smbfs_host_delegate = delegate; |
| return std::move(mock_mounter); |
| })); |
| |
| base::RunLoop run_loop; |
| EXPECT_CALL(*mock_mounter, Mount(_)) |
| .WillOnce([this, &smbfs_host_delegate, &smbfs_remote, |
| &smbfs_delegate_remote, |
| &run_loop](smbfs::SmbFsMounter::DoneCallback callback) { |
| std::move(callback).Run( |
| smbfs::mojom::MountError::kOk, |
| std::make_unique<smbfs::SmbFsHost>( |
| MakeMountPoint(base::FilePath(kMountPath)), smbfs_host_delegate, |
| std::move(smbfs_remote), |
| smbfs_delegate_remote.BindNewPipeAndPassReceiver())); |
| run_loop.Quit(); |
| }); |
| |
| run_loop.Run(); |
| |
| // Unmounting should remove the saved share, and ask smbfs to remove any saved |
| // credentials. |
| base::RunLoop run_loop2; |
| EXPECT_CALL(smbfs_impl, RemoveSavedCredentials(_)) |
| .WillOnce( |
| [](smbfs::mojom::SmbFs::RemoveSavedCredentialsCallback callback) { |
| std::move(callback).Run(true /* success */); |
| }); |
| EXPECT_CALL(smbfs_impl, OnDisconnect()) |
| .WillOnce(base::test::RunClosure(run_loop2.QuitClosure())); |
| smb_service_->UnmountSmbFs(base::FilePath(kMountPath)); |
| run_loop2.Run(); |
| |
| SmbPersistedShareRegistry registry(profile_); |
| absl::optional<SmbShareInfo> info = registry.Get(SmbUrl(kShareUrl)); |
| EXPECT_FALSE(info); |
| EXPECT_TRUE(registry.GetAll().empty()); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, MountExcessiveShares) { |
| // The maxmium number of smbfs shares that can be mounted simultaneously. |
| // Should match the definition in smb_service.cc. |
| const size_t kMaxSmbFsShares = 16; |
| CreateService(profile_); |
| WaitForSetupComplete(); |
| |
| // Check: It is possible to mount the maximum number of shares. |
| for (size_t i = 0; i < kMaxSmbFsShares; ++i) { |
| const std::string share_path = |
| std::string(kSharePath) + base::NumberToString(i); |
| const std::string mount_path = |
| std::string(kMountPath) + base::NumberToString(i); |
| ignore_result(MountBasicShare(share_path, mount_path, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| }))); |
| } |
| |
| // Check: After mounting the maximum number of shares, requesting to mount an |
| // additional share should fail. |
| const std::string share_path = |
| std::string(kSharePath) + base::NumberToString(kMaxSmbFsShares); |
| const std::string mount_path = |
| std::string(kMountPath) + base::NumberToString(kMaxSmbFsShares); |
| ignore_result(MountBasicShare( |
| share_path, mount_path, base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kTooManyOpened, result); |
| }))); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, GetSmbFsShareForPath) { |
| CreateService(profile_); |
| WaitForSetupComplete(); |
| |
| ignore_result(MountBasicShare(kSharePath, kMountPath, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| }))); |
| ignore_result(MountBasicShare(kSharePath2, kMountPath2, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| }))); |
| |
| SmbFsShare* share = |
| smb_service_->GetSmbFsShareForPath(base::FilePath(kMountPath)); |
| EXPECT_EQ(share->mount_path(), base::FilePath(kMountPath)); |
| share = smb_service_->GetSmbFsShareForPath( |
| base::FilePath(kMountPath).Append("foo")); |
| EXPECT_EQ(share->mount_path(), base::FilePath(kMountPath)); |
| |
| share = smb_service_->GetSmbFsShareForPath(base::FilePath(kMountPath2)); |
| EXPECT_EQ(share->mount_path(), base::FilePath(kMountPath2)); |
| share = smb_service_->GetSmbFsShareForPath( |
| base::FilePath(kMountPath2).Append("bar/baz")); |
| EXPECT_EQ(share->mount_path(), base::FilePath(kMountPath2)); |
| |
| EXPECT_FALSE( |
| smb_service_->GetSmbFsShareForPath(base::FilePath("/share/mount"))); |
| EXPECT_FALSE(smb_service_->GetSmbFsShareForPath( |
| base::FilePath("/share/mount/third_path"))); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, MountDuplicate) { |
| CreateService(profile_); |
| WaitForSetupComplete(); |
| |
| ignore_result(MountBasicShare(kSharePath, kMountPath, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| }))); |
| |
| // A second mount with the same share path should fail. |
| ignore_result(MountBasicShare( |
| kSharePath, kMountPath2, base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kMountExists, result); |
| }))); |
| |
| // Unmounting and mounting again should succeed. |
| smb_service_->UnmountSmbFs(base::FilePath(kMountPath)); |
| ignore_result(MountBasicShare(kSharePath, kMountPath2, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| }))); |
| } |
| |
| } // namespace smb_client |
| } // namespace ash |