| // Copyright 2017 The Chromium Authors |
| // 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 "base/json/json_reader.h" |
| #include "base/strings/string_number_conversions.h" |
| #include "base/test/gmock_callback_support.h" |
| #include "chrome/browser/ash/smb_client/smb_service_test_base.h" |
| #include "chrome/common/pref_names.h" |
| #include "components/prefs/pref_service.h" |
| #include "storage/browser/file_system/external_mount_points.h" |
| #include "testing/gtest/include/gtest/gtest.h" |
| |
| namespace ash::smb_client { |
| |
| namespace { |
| |
| inline constexpr char kTestUser[] = "foobar"; |
| inline constexpr char kTestPassword[] = "my_secret_password"; |
| inline constexpr char kTestDomain[] = "EXAMPLE.COM"; |
| inline constexpr char kSharePath2[] = "\\\\server2\\second_share"; |
| inline constexpr char kShareUrl[] = "smb://server/foobar"; |
| inline constexpr char kInvalidShareUrl[] = "smb://server"; |
| inline constexpr char kMountPath2[] = "/share/mount/second_path"; |
| |
| } // namespace |
| |
| class SmbServiceWithSmbfsTest : public SmbServiceBaseTest {}; |
| |
| 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; |
| auto 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_TRUE(options.allow_ntlm); |
| 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_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()); |
| std::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; |
| auto 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_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()); |
| std::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, MountPreconfigured) { |
| 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; |
| auto 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, MountInvalidPreconfigured) { |
| const char kPreconfiguredShares[] = |
| R"([{"mode":"pre_mount","share_url":"\\\\preconfigured"}])"; |
| auto parsed_shares = base::JSONReader::Read(kPreconfiguredShares); |
| ASSERT_TRUE(parsed_shares); |
| profile()->GetPrefs()->Set(prefs::kNetworkFileSharesPreconfiguredShares, |
| *parsed_shares); |
| |
| CreateService(profile()); |
| |
| base::RunLoop run_loop; |
| smb_service->SetRestoredShareMountDoneCallbackForTesting( |
| base::BindLambdaForTesting([&run_loop](SmbMountResult mount_result, |
| const base::FilePath& mount_path) { |
| EXPECT_EQ(mount_result, SmbMountResult::kInvalidUrl); |
| 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; |
| auto 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_TRUE(options.allow_ntlm); |
| 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()); |
| std::optional<SmbShareInfo> info = registry.Get(SmbUrl(kShareUrl)); |
| EXPECT_FALSE(info); |
| EXPECT_TRUE(registry.GetAll().empty()); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, MountInvalidSaved) { |
| const std::vector<uint8_t> kSalt = {0, 1, 2, 3, 4, 5, 6, 7, 8, 9}; |
| // Save an (invalid) share in profile. This can't occur in practice. |
| { |
| SmbPersistedShareRegistry registry(profile()); |
| SmbShareInfo info(SmbUrl(kInvalidShareUrl), kDisplayName, kTestUser, |
| kTestDomain, /*use_kerberos=*/false, kSalt); |
| registry.Save(info); |
| } |
| |
| CreateService(profile()); |
| |
| base::RunLoop run_loop; |
| smb_service->SetRestoredShareMountDoneCallbackForTesting( |
| base::BindLambdaForTesting([&run_loop](SmbMountResult mount_result, |
| const base::FilePath& mount_path) { |
| EXPECT_EQ(mount_result, SmbMountResult::kInvalidUrl); |
| run_loop.Quit(); |
| })); |
| |
| run_loop.Run(); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, MountExcessiveShares) { |
| // The maximum 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); |
| std::ignore = 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); |
| std::ignore = MountBasicShare( |
| share_path, mount_path, base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kTooManyOpened, result); |
| })); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, GetSmbFsShareForPath) { |
| CreateService(profile()); |
| WaitForSetupComplete(); |
| |
| std::ignore = MountBasicShare(kSharePath, kMountPath, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| })); |
| std::ignore = 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(); |
| |
| std::ignore = MountBasicShare(kSharePath, kMountPath, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| })); |
| |
| // A second mount with the same share path should fail. |
| std::ignore = MountBasicShare( |
| kSharePath, kMountPath2, base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kMountExists, result); |
| })); |
| |
| // Unmounting and mounting again should succeed. |
| smb_service->UnmountSmbFs(base::FilePath(kMountPath)); |
| std::ignore = MountBasicShare(kSharePath, kMountPath2, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| })); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, IsAnySmbShareAdded) { |
| CreateService(profile()); |
| WaitForSetupComplete(); |
| EXPECT_FALSE(smb_service->IsAnySmbShareConfigured()); |
| |
| // Add a share |
| std::ignore = MountBasicShare(kSharePath, kMountPath, |
| base::BindOnce([](SmbMountResult result) { |
| EXPECT_EQ(SmbMountResult::kSuccess, result); |
| })); |
| |
| EXPECT_TRUE(smb_service->IsAnySmbShareConfigured()); |
| } |
| |
| TEST_F(SmbServiceWithSmbfsTest, IsAnySmbShareConfigured) { |
| // Add a 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()); |
| EXPECT_TRUE(smb_service->IsAnySmbShareConfigured()); |
| } |
| |
| } // namespace ash::smb_client |