// 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/test/gmock_callback_support.h"
#include "chrome/browser/ash/smb_client/smb_service_test_base.h"
#include "chrome/common/pref_names.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