blob: 808362fc20eda6dff7c329b772d849bf0de7ea30 [file] [log] [blame]
// Copyright 2017 The Chromium OS 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 <memory>
#include <utility>
#include <base/bind.h>
#include <base/check_op.h>
#include <base/files/file_util.h>
#include <base/test/simple_test_tick_clock.h>
#include <dbus/mock_bus.h>
#include <dbus/object_path.h>
#include <dbus/smbprovider/dbus-constants.h>
#include <gtest/gtest.h>
#include "smbprovider/fake_kerberos_artifact_client.h"
#include "smbprovider/fake_samba_interface.h"
#include "smbprovider/fake_samba_proxy.h"
#include "smbprovider/iterator/directory_iterator.h"
#include "smbprovider/kerberos_artifact_synchronizer.h"
#include "smbprovider/metadata_cache.h"
#include "smbprovider/mount_config.h"
#include "smbprovider/mount_manager.h"
#include "smbprovider/netbios_packet_parser.h"
#include "smbprovider/proto_bindings/directory_entry.pb.h"
#include "smbprovider/smbprovider.h"
#include "smbprovider/smbprovider_helper.h"
#include "smbprovider/smbprovider_test_helper.h"
#include "smbprovider/temp_file_manager.h"
namespace smbprovider {
namespace {
using brillo::dbus_utils::DBusObject;
// arbitary D-Bus
constexpr int32_t kDBusSerial = 123;
ErrorType CastError(int error) {
EXPECT_GE(error, 0);
return static_cast<ErrorType>(error);
}
DirectoryEntryListProto GetDirectoryEntryListProtoFromBlob(
const ProtoBlob& blob) {
DirectoryEntryListProto entries;
EXPECT_TRUE(entries.ParseFromArray(blob.data(), blob.size()));
return entries;
}
HostnamesProto GetHostnamesProtoFromBlob(const ProtoBlob& blob) {
HostnamesProto hostnames_proto;
EXPECT_TRUE(hostnames_proto.ParseFromArray(blob.data(), blob.size()));
return hostnames_proto;
}
void ExpectKerberosCallback(bool expected_result,
std::unique_ptr<dbus::Response> response) {
EXPECT_TRUE(response.get());
dbus::MessageReader reader(response.get());
bool result;
EXPECT_TRUE(reader.PopBool(&result));
EXPECT_EQ(expected_result, result);
}
} // namespace
class SmbProviderTest : public testing::Test {
public:
std::unique_ptr<SambaInterface> SambaInterfaceFactoryFunction(
FakeSambaInterface* fake_samba,
MountManager* mount_manager,
const MountConfig& mount_config) {
enable_ntlm_ = mount_config.enable_ntlm;
return std::make_unique<FakeSambaProxy>(fake_samba);
}
SmbProviderTest() { SetUpSmbProvider(false /* enable_metadata_cache */); }
SmbProviderTest(const SmbProviderTest&) = delete;
SmbProviderTest& operator=(const SmbProviderTest&) = delete;
protected:
using DirEntries = std::vector<smbc_dirent>;
// Sets up SmbProvider with caching set to |enable_metadata_cache|. This is
// called by default before each test with caching disabled. Pass true and
// call as the first line in a test to enable caching.
void SetUpSmbProvider(bool enable_metadata_cache) {
fake_samba_ = std::make_unique<FakeSambaInterface>();
auto tick_clock = std::make_unique<base::SimpleTestTickClock>();
fake_tick_clock_ = tick_clock.get();
auto mount_tracker = std::make_unique<MountTracker>(std::move(tick_clock),
enable_metadata_cache);
auto samba_interface_factory =
base::BindRepeating(&SmbProviderTest::SambaInterfaceFactoryFunction,
base::Unretained(this), fake_samba_.get());
auto mount_manager_ptr = std::make_unique<MountManager>(
std::move(mount_tracker), samba_interface_factory);
mount_manager_ = mount_manager_ptr.get();
auto fake_artifact_client = std::make_unique<FakeKerberosArtifactClient>();
kerberos_client_ = fake_artifact_client.get();
// Make sure there is a fresh directory in the case this is called more
// than once.
if (krb_temp_dir_.IsValid()) {
EXPECT_TRUE(krb_temp_dir_.Delete());
}
EXPECT_TRUE(krb_temp_dir_.CreateUniqueTempDir());
if (daemon_store_temp_dir_.IsValid()) {
EXPECT_TRUE(daemon_store_temp_dir_.Delete());
}
EXPECT_TRUE(daemon_store_temp_dir_.CreateUniqueTempDir());
krb5_conf_path_ = CreateKrb5ConfPath(krb_temp_dir_.GetPath());
krb5_ccache_path_ = CreateKrb5CCachePath(krb_temp_dir_.GetPath());
auto kerberos_artifact_synchronizer =
std::make_unique<KerberosArtifactSynchronizer>(
krb5_conf_path_, krb5_ccache_path_, std::move(fake_artifact_client),
false /* allow_credentials_update */);
kerberos_synchronizer_ = kerberos_artifact_synchronizer.get();
const dbus::ObjectPath object_path("/object/path");
smbprovider_ = std::make_unique<SmbProvider>(
std::make_unique<DBusObject>(nullptr, mock_bus_, object_path),
std::move(mount_manager_ptr), std::move(kerberos_artifact_synchronizer),
daemon_store_temp_dir_.GetPath());
metadata_cache_ = std::make_unique<MetadataCache>(
fake_tick_clock_,
base::TimeDelta::FromMicroseconds(kMetadataCacheLifetimeMicroseconds),
MetadataCache::Mode::kDisabled);
}
// Helper method that asserts there are no entries that have not been
// closed.
void ExpectNoOpenEntries() { EXPECT_FALSE(fake_samba_->HasOpenEntries()); }
bool GetRootPath(int32_t mount_id, std::string* mount_path) const {
return mount_manager_->GetFullPath(mount_id, "" /* entry_path */,
mount_path);
}
void CreateDaemonStoreForUser(const std::string& user_hash) {
base::File::Error error = base::File::FILE_OK;
EXPECT_TRUE(base::CreateDirectoryAndGetError(
daemon_store_temp_dir_.GetPath().Append(base::FilePath(user_hash)),
&error))
<< "CreateDaemonStoreForUser error: " << error;
}
std::string krb5_conf_path_;
std::string krb5_ccache_path_;
base::ScopedTempDir krb_temp_dir_;
base::ScopedTempDir daemon_store_temp_dir_;
scoped_refptr<dbus::MockBus> mock_bus_ =
new dbus::MockBus(dbus::Bus::Options());
std::unique_ptr<SmbProvider> smbprovider_;
std::unique_ptr<FakeSambaInterface> fake_samba_;
base::SimpleTestTickClock* fake_tick_clock_;
MountManager* mount_manager_;
TempFileManager temp_file_manager_;
FakeKerberosArtifactClient* kerberos_client_;
KerberosArtifactSynchronizer* kerberos_synchronizer_;
// |metadata_cache| is used to test the GetEntries method
std::unique_ptr<MetadataCache> metadata_cache_;
bool enable_ntlm_ = false;
};
TEST_F(SmbProviderTest, GetSharesFailsOnEmptyProto) {
ProtoBlob empty_blob;
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(empty_blob, &error, &result);
EXPECT_EQ(ERROR_DBUS_PARSE_FAILED, CastError(error));
}
TEST_F(SmbProviderTest, GetSharesFailsOnNonExistentServer) {
ProtoBlob blob = CreateGetSharesOptionsBlob("smb://0.0.0.1");
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_NOT_FOUND, CastError(error));
}
TEST_F(SmbProviderTest, GetSharesSucceedsOnEmptyServer) {
const std::string server_url = "smb://192.168.0.1";
fake_samba_->AddServer(server_url);
ProtoBlob blob = CreateGetSharesOptionsBlob(server_url);
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_OK, CastError(error));
EXPECT_TRUE(GetDirectoryEntryListProtoFromBlob(result).entries().empty());
}
TEST_F(SmbProviderTest, GetSharesSucceedsWithSingleShare) {
const std::string server_url = "smb://192.168.0.1";
const std::string share = "share1";
fake_samba_->AddServer(server_url);
fake_samba_->AddShare(server_url + "/" + share);
ProtoBlob blob = CreateGetSharesOptionsBlob(server_url);
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_OK, CastError(error));
DirectoryEntryListProto dir_entry_list =
GetDirectoryEntryListProtoFromBlob(result);
EXPECT_EQ(dir_entry_list.entries().size(), 1);
const DirectoryEntryProto& entry = dir_entry_list.entries(0);
EXPECT_EQ(entry.name(), share);
EXPECT_TRUE(entry.is_directory());
}
TEST_F(SmbProviderTest, GetSharesSucceedsWithMultipleShares) {
const std::string server_url = "smb://192.168.0.1";
const std::string share1 = "share1";
const std::string share2 = "share2";
fake_samba_->AddServer(server_url);
fake_samba_->AddShare(server_url + "/" + share1);
fake_samba_->AddShare(server_url + "/" + share2);
ProtoBlob blob = CreateGetSharesOptionsBlob(server_url);
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_OK, CastError(error));
DirectoryEntryListProto dir_entry_list =
GetDirectoryEntryListProtoFromBlob(result);
EXPECT_EQ(dir_entry_list.entries().size(), 2);
const DirectoryEntryProto& entry1 = dir_entry_list.entries(0);
EXPECT_EQ(entry1.name(), share1);
EXPECT_TRUE(entry1.is_directory());
const DirectoryEntryProto& entry2 = dir_entry_list.entries(1);
EXPECT_EQ(entry2.name(), share2);
EXPECT_TRUE(entry2.is_directory());
}
TEST_F(SmbProviderTest, GetSharesDoesntReturnSelfAndParentEntries) {
const std::string server_url = "smb://192.168.0.1";
const std::string share1 = "share1";
fake_samba_->AddServer(server_url);
fake_samba_->AddShare(server_url + "/" + share1);
// These shouldn't be returned by GetShares.
fake_samba_->AddShare(server_url + "/.");
fake_samba_->AddShare(server_url + "/..");
ProtoBlob blob = CreateGetSharesOptionsBlob(server_url);
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_OK, CastError(error));
DirectoryEntryListProto dir_entry_list =
GetDirectoryEntryListProtoFromBlob(result);
EXPECT_EQ(dir_entry_list.entries().size(), 1);
const DirectoryEntryProto& entry1 = dir_entry_list.entries(0);
EXPECT_EQ(entry1.name(), share1);
EXPECT_TRUE(entry1.is_directory());
}
TEST_F(SmbProviderTest, GetSharesDoesntReturnNonShareEntries) {
const std::string server_url = "smb://192.168.0.1";
const std::string share1 = "share1";
fake_samba_->AddServer(server_url);
fake_samba_->AddShare(server_url + "/" + share1);
// These shouldn't be returned by GetShares since they aren't shares.
fake_samba_->AddDirectory(server_url + "/dir");
fake_samba_->AddFile(server_url + "/file");
ProtoBlob blob = CreateGetSharesOptionsBlob(server_url);
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_OK, CastError(error));
DirectoryEntryListProto dir_entry_list =
GetDirectoryEntryListProtoFromBlob(result);
EXPECT_EQ(dir_entry_list.entries().size(), 1);
const DirectoryEntryProto& entry1 = dir_entry_list.entries(0);
EXPECT_EQ(entry1.name(), share1);
EXPECT_TRUE(entry1.is_directory());
}
TEST_F(SmbProviderTest, GetSharesReturnsShareContainingDirectory) {
const std::string server_url = "smb://192.168.0.1";
const std::string share1 = "share1";
fake_samba_->AddServer(server_url);
fake_samba_->AddShare(server_url + "/" + share1);
// Add a directory in the share.
fake_samba_->AddDirectory(server_url + "/" + share1 + "/dir");
ProtoBlob blob = CreateGetSharesOptionsBlob(server_url);
int32_t error;
ProtoBlob result;
smbprovider_->GetShares(blob, &error, &result);
EXPECT_EQ(ERROR_OK, CastError(error));
DirectoryEntryListProto dir_entry_list =
GetDirectoryEntryListProtoFromBlob(result);
EXPECT_EQ(dir_entry_list.entries().size(), 1);
const DirectoryEntryProto& entry1 = dir_entry_list.entries(0);
EXPECT_EQ(entry1.name(), share1);
EXPECT_TRUE(entry1.is_directory());
}
TEST_F(SmbProviderTest, SetupKerberosWritesKerberosFilesSuccessfully) {
const std::string user = "test user";
const std::string krb5cc = "test creds";
const std::string krb5conf = "test conf";
authpolicy::KerberosFiles kerberos_files =
CreateKerberosFilesProto(krb5cc, krb5conf);
kerberos_client_->AddKerberosFiles(user, kerberos_files);
dbus::MethodCall method_call(kSmbProviderInterface, "SetupKerberos");
method_call.SetSerial(kDBusSerial);
SmbProvider::SetupKerberosCallback callback =
std::make_unique<brillo::dbus_utils::DBusMethodResponse<bool>>(
&method_call,
base::BindOnce(&ExpectKerberosCallback, true /* expected_result*/));
smbprovider_->SetupKerberos(std::move(callback), user);
ExpectFileEqual(krb5_conf_path_, krb5conf);
ExpectFileEqual(krb5_ccache_path_, krb5cc);
}
TEST_F(SmbProviderTest, SetupKerberosFailsWhenKerberosFilesDoNotExist) {
const std::string user = "test user";
dbus::MethodCall method_call(kSmbProviderInterface, "SetupKerberos");
method_call.SetSerial(kDBusSerial);
SmbProvider::SetupKerberosCallback callback =
std::make_unique<brillo::dbus_utils::DBusMethodResponse<bool>>(
&method_call,
base::BindOnce(&ExpectKerberosCallback, false /* expected_result*/));
smbprovider_->SetupKerberos(std::move(callback), user);
}
TEST_F(SmbProviderTest, ParseNetBiosPacketSucceedsOnValidPacket) {
const std::string name_string("testname");
const std::vector<uint8_t> name(name_string.begin(), name_string.end());
const uint8_t name_length(name.size());
const uint16_t transaction_id(123);
const std::string hostname_1 = "hostname1";
const std::string hostname_2 = "hostname2";
const std::vector<std::vector<uint8_t>> hostnames = {
CreateValidNetBiosHostname(hostname_1, netbios::kFileServerNodeType),
CreateValidNetBiosHostname(hostname_2, netbios::kFileServerNodeType)};
const std::vector<uint8_t> valid_packet = CreateNetBiosResponsePacket(
hostnames, name_length, name, transaction_id, 0x20 /* response_type */);
ProtoBlob blob =
smbprovider_->ParseNetBiosPacket(valid_packet, transaction_id);
const HostnamesProto hostnames_proto = GetHostnamesProtoFromBlob(blob);
EXPECT_EQ(2, hostnames_proto.hostnames().size());
EXPECT_EQ(hostname_1, hostnames_proto.hostnames(0));
EXPECT_EQ(hostname_2, hostnames_proto.hostnames(1));
}
TEST_F(SmbProviderTest, ParseNetBiosPacketFailsOnInvalidPacket) {
const std::vector<uint8_t> invalid_packet;
ProtoBlob blob =
smbprovider_->ParseNetBiosPacket(invalid_packet, 0 /* transaction_id */);
const HostnamesProto hostnames_proto = GetHostnamesProtoFromBlob(blob);
EXPECT_EQ(0, hostnames_proto.hostnames().size());
}
} // namespace smbprovider