Skip to content

Commit

Permalink
[tests] Add permission utils tests
Browse files Browse the repository at this point in the history
  • Loading branch information
Sploder12 committed Dec 4, 2024
1 parent 8bc64cb commit 68e6c0f
Show file tree
Hide file tree
Showing 5 changed files with 252 additions and 16 deletions.
13 changes: 8 additions & 5 deletions include/multipass/utils/permission_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,10 +18,11 @@
#ifndef MULTIPASS_PERMISSION_UTILS_H
#define MULTIPASS_PERMISSION_UTILS_H

#include <multipass/file_ops.h>
#include <multipass/platform.h>
#include <multipass/singleton.h>

#include <QFileDevice>
#include <filesystem>

#define MP_PERMISSIONS multipass::PermissionUtils::instance()

namespace multipass
Expand All @@ -30,13 +31,15 @@ namespace multipass
class PermissionUtils : public Singleton<PermissionUtils>
{
public:
using Path = std::filesystem::path;

PermissionUtils(const PrivatePass&) noexcept;

virtual void set_permissions(const fs::path& path, const QFileDevice::Permissions& permissions) const;
virtual void take_ownership(const fs::path& path, bool root = true) const;
virtual void set_permissions(const Path& path, const QFileDevice::Permissions& permissions) const;
virtual void take_ownership(const Path& path, bool root = true) const;

// sets owner to root and sets permissions such that only owner has access.
virtual void restrict_permissions(const fs::path& path) const;
virtual void restrict_permissions(const Path& path) const;
};
} // namespace multipass

Expand Down
21 changes: 13 additions & 8 deletions src/utils/permission_utils.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,20 +17,25 @@

#include <multipass/utils/permission_utils.h>

#include <multipass/file_ops.h>
#include <multipass/platform.h>

namespace mp = multipass;
namespace fs = mp::fs;

using Path = mp::PermissionUtils::Path;

namespace
{
void set_single_permissions(const fs::path& path, const QFileDevice::Permissions& permissions)
void set_single_permissions(const Path& path, const QFileDevice::Permissions& permissions)
{
QString qpath = QString::fromUtf8(path.u8string());

if (!MP_PLATFORM.set_permissions(qpath, permissions))
throw std::runtime_error(fmt::format("Cannot set permissions for '{}'", path.string()));
}

void set_single_owner(const fs::path& path, bool root)
void set_single_owner(const Path& path, bool root)
{
QString qpath = QString::fromUtf8(path.u8string());

Expand All @@ -39,7 +44,7 @@ void set_single_owner(const fs::path& path, bool root)
}

// only exists because MP_FILEOPS doesn't overload the throwing variaions of std::filesystem functions
void throw_if_error(const fs::path& path, const std::error_code& ec)
void throw_if_error(const Path& path, const std::error_code& ec)
{
if (ec)
throw std::system_error(
Expand All @@ -49,7 +54,7 @@ void throw_if_error(const fs::path& path, const std::error_code& ec)

// recursively iterates over all files in folder and applies a function that takes a path
template <class Func>
void apply_on_files(const fs::path& path, Func&& func)
void apply_on_files(const Path& path, Func&& func)
{
std::error_code ec{};
if (!MP_FILEOPS.exists(path, ec) || ec)
Expand All @@ -63,7 +68,7 @@ void apply_on_files(const fs::path& path, Func&& func)
auto dir_iterator = MP_FILEOPS.recursive_dir_iterator(path, ec);
throw_if_error(path, ec);

if (!dir_iterator)
if (!dir_iterator) [[unlikely]]
throw std::runtime_error(fmt::format("Cannot iterate over directory '{}'", path.string()));

while (dir_iterator->hasNext())
Expand All @@ -83,17 +88,17 @@ mp::PermissionUtils::PermissionUtils(const Singleton<PermissionUtils>::PrivatePa
{
}

void mp::PermissionUtils::set_permissions(const fs::path& path, const QFileDevice::Permissions& permissions) const
void mp::PermissionUtils::set_permissions(const Path& path, const QFileDevice::Permissions& permissions) const
{
apply_on_files(path, [&](const fs::path& apply_path) { set_single_permissions(apply_path, permissions); });
}

void mp::PermissionUtils::take_ownership(const fs::path& path, bool root) const
void mp::PermissionUtils::take_ownership(const Path& path, bool root) const
{
apply_on_files(path, [&](const fs::path& apply_path) { set_single_owner(apply_path, root); });
}

void mp::PermissionUtils::restrict_permissions(const fs::path& path) const
void mp::PermissionUtils::restrict_permissions(const Path& path) const
{
apply_on_files(path, [&](const fs::path& apply_path) {
set_single_owner(apply_path, true);
Expand Down
1 change: 1 addition & 0 deletions tests/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -124,6 +124,7 @@ add_executable(multipass_tests
test_sftp_utils.cpp
test_file_ops.cpp
test_recursive_dir_iter.cpp
test_permission_utils.cpp
)

target_include_directories(multipass_tests
Expand Down
6 changes: 3 additions & 3 deletions tests/mock_permission_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -35,10 +35,10 @@ class MockPermissionUtils : public PermissionUtils

MOCK_METHOD(void,
set_permissions,
(const fs::path& path, const QFileDevice::Permissions& permissions),
(const Path& path, const QFileDevice::Permissions& permissions),
(const, override));
MOCK_METHOD(void, take_ownership, (const fs::path& path, bool root), (const, override));
MOCK_METHOD(void, restrict_permissions, (const fs::path& path), (const, override));
MOCK_METHOD(void, take_ownership, (const Path& path, bool root), (const, override));
MOCK_METHOD(void, restrict_permissions, (const Path& path), (const, override));

MP_MOCK_SINGLETON_BOILERPLATE(MockPermissionUtils, PermissionUtils);
};
Expand Down
227 changes: 227 additions & 0 deletions tests/test_permission_utils.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,227 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#include <Poco/Path.h>

#include "mock_file_ops.h"
#include "mock_permission_utils.h"
#include "mock_platform.h"
#include "mock_recursive_dir_iterator.h"

namespace mp = multipass;
namespace mpt = mp::test;
using namespace testing;

namespace
{
struct TestPermissionUtils : public Test
{
const mpt::MockFileOps::GuardedMock guarded_mock_file_ops = mpt::MockFileOps::inject<StrictMock>();
mpt::MockFileOps& mock_file_ops = *guarded_mock_file_ops.first;
const mpt::MockPlatform::GuardedMock guarded_mock_platform = mpt::MockPlatform::inject<StrictMock>();
mpt::MockPlatform& mock_platform = *guarded_mock_platform.first;

const mp::PermissionUtils::Path test_path{"test_path"};
const QString test_qpath{QString::fromUtf8(test_path.u8string())};
static constexpr QFileDevice::Permissions test_permissions{QFileDevice::Permission::ReadOwner};
static constexpr QFileDevice::Permissions restricted_permissions{QFileDevice::Permission::ReadOwner |
QFileDevice::Permission::WriteOwner};
};

struct TestPermissionUtilsNoFile : public TestPermissionUtils
{
TestPermissionUtilsNoFile()
{
EXPECT_CALL(mock_file_ops, exists(test_path, _)).WillOnce(Return(false));
}
};

TEST_F(TestPermissionUtilsNoFile, set_permissions_throws_when_file_non_existant)
{
MP_EXPECT_THROW_THAT(MP_PERMISSIONS.set_permissions(test_path, test_permissions),
std::runtime_error,
mpt::match_what(HasSubstr(test_path.string())));
}

TEST_F(TestPermissionUtilsNoFile, take_ownership_throws_when_file_non_existant)
{
MP_EXPECT_THROW_THAT(MP_PERMISSIONS.take_ownership(test_path),
std::runtime_error,
mpt::match_what(HasSubstr(test_path.string())));
}

TEST_F(TestPermissionUtilsNoFile, restrict_permissions_throws_when_file_non_existant)
{
MP_EXPECT_THROW_THAT(MP_PERMISSIONS.restrict_permissions(test_path),
std::runtime_error,
mpt::match_what(AllOf(HasSubstr("nonexistent file"), HasSubstr(test_path.string()))));
}

struct TestPermissionUtilsFile : public TestPermissionUtils
{
TestPermissionUtilsFile()
{
EXPECT_CALL(mock_file_ops, exists(test_path, _)).WillOnce(Return(true));
EXPECT_CALL(mock_file_ops, is_directory(test_path, _)).WillRepeatedly(Return(false));
}
};

TEST_F(TestPermissionUtilsFile, set_permissions_sets_permissions)
{
EXPECT_CALL(mock_platform, set_permissions(test_qpath, test_permissions)).WillOnce(Return(true));

MP_PERMISSIONS.set_permissions(test_path, test_permissions);
}

TEST_F(TestPermissionUtilsFile, set_permissions_throws_on_failure)
{
EXPECT_CALL(mock_platform, set_permissions(test_qpath, test_permissions)).WillOnce(Return(false));

MP_EXPECT_THROW_THAT(MP_PERMISSIONS.set_permissions(test_path, test_permissions),
std::runtime_error,
mpt::match_what(AllOf(HasSubstr("Cannot set permissions"), HasSubstr(test_path.string()))));
}

TEST_F(TestPermissionUtilsFile, take_ownership_takes_ownership)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, true)).WillOnce(Return(true));

MP_PERMISSIONS.take_ownership(test_path, true);
}

TEST_F(TestPermissionUtilsFile, take_ownership_takes_user_ownership)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, false)).WillOnce(Return(true));

MP_PERMISSIONS.take_ownership(test_path, false);
}

TEST_F(TestPermissionUtilsFile, take_ownership_throws_on_failure)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, _)).WillOnce(Return(false));

MP_EXPECT_THROW_THAT(MP_PERMISSIONS.take_ownership(test_path),
std::runtime_error,
mpt::match_what(AllOf(HasSubstr("Cannot set owner"), HasSubstr(test_path.string()))));
}

TEST_F(TestPermissionUtilsFile, restrict_permissions_restricts_permissions)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, true)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, set_permissions(test_qpath, restricted_permissions)).WillOnce(Return(true));

MP_PERMISSIONS.restrict_permissions(test_path);
}

struct TestPermissionUtilsDir : public TestPermissionUtils
{
TestPermissionUtilsDir()
{
EXPECT_CALL(mock_file_ops, exists(test_path, _)).WillRepeatedly(Return(true));
EXPECT_CALL(mock_file_ops, is_directory(test_path, _)).WillOnce(Return(true));

EXPECT_CALL(entry1, path).WillRepeatedly(ReturnRef(path1));
EXPECT_CALL(entry2, path).WillRepeatedly(ReturnRef(path2));

auto iter = std::make_unique<mpt::MockRecursiveDirIterator>();
auto iter_p = iter.get();

EXPECT_CALL(*iter_p, hasNext).WillOnce(Return(true)).WillOnce(Return(true)).WillRepeatedly(Return(false));
EXPECT_CALL(*iter_p, next).WillOnce(ReturnRef(entry1)).WillOnce(ReturnRef(entry2));
EXPECT_CALL(mock_file_ops, recursive_dir_iterator(test_path, _)).WillOnce(Return(std::move(iter)));
}

const mp::PermissionUtils::Path path1{"Hello.txt"};
const QString qpath1{QString::fromUtf8(path1.u8string())};
mpt::MockDirectoryEntry entry1;

const mp::PermissionUtils::Path path2{"World.txt"};
const QString qpath2{QString::fromUtf8(path2.u8string())};
mpt::MockDirectoryEntry entry2;
};

TEST_F(TestPermissionUtilsDir, set_permissions_iterates_dir)
{
EXPECT_CALL(mock_platform, set_permissions(test_qpath, test_permissions)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, set_permissions(qpath1, test_permissions)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, set_permissions(qpath2, test_permissions)).WillOnce(Return(true));

MP_PERMISSIONS.set_permissions(test_path, test_permissions);
}

TEST_F(TestPermissionUtilsDir, take_ownership_iterates_dir)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, false)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, take_ownership(qpath1, false)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, take_ownership(qpath2, false)).WillOnce(Return(true));

MP_PERMISSIONS.take_ownership(test_path, false);
}

TEST_F(TestPermissionUtilsDir, restrict_permissions_iterates_dir)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, true)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, take_ownership(qpath1, true)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, take_ownership(qpath2, true)).WillOnce(Return(true));

EXPECT_CALL(mock_platform, set_permissions(test_qpath, restricted_permissions)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, set_permissions(qpath1, restricted_permissions)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, set_permissions(qpath2, restricted_permissions)).WillOnce(Return(true));

MP_PERMISSIONS.restrict_permissions(test_path);
}

struct TestPermissionUtilsBadDir : public TestPermissionUtils
{
TestPermissionUtilsBadDir()
{
EXPECT_CALL(mock_file_ops, exists(test_path, _)).WillRepeatedly(Return(true));
EXPECT_CALL(mock_file_ops, is_directory(test_path, _)).WillOnce(Return(true));

EXPECT_CALL(mock_file_ops, recursive_dir_iterator(test_path, _)).WillOnce(Return(nullptr));
}
};

TEST_F(TestPermissionUtilsBadDir, set_permissions_throws_on_broken_iterator)
{
EXPECT_CALL(mock_platform, set_permissions(test_qpath, test_permissions)).WillOnce(Return(true));

MP_EXPECT_THROW_THAT(MP_PERMISSIONS.set_permissions(test_path, test_permissions),
std::runtime_error,
mpt::match_what(AllOf(HasSubstr("Cannot iterate"), HasSubstr(test_path.string()))));
}

TEST_F(TestPermissionUtilsBadDir, take_ownership_throws_on_broken_iterator)
{
EXPECT_CALL(mock_platform, take_ownership(test_qpath, false)).WillOnce(Return(true));

MP_EXPECT_THROW_THAT(MP_PERMISSIONS.take_ownership(test_path, false),
std::runtime_error,
mpt::match_what(AllOf(HasSubstr("Cannot iterate"), HasSubstr(test_path.string()))));
}

TEST_F(TestPermissionUtilsBadDir, restrict_permissions_throws_on_broken_iterator)
{
EXPECT_CALL(mock_platform, set_permissions(test_qpath, restricted_permissions)).WillOnce(Return(true));
EXPECT_CALL(mock_platform, take_ownership(test_qpath, true)).WillOnce(Return(true));

MP_EXPECT_THROW_THAT(MP_PERMISSIONS.restrict_permissions(test_path),
std::runtime_error,
mpt::match_what(AllOf(HasSubstr("Cannot iterate"), HasSubstr(test_path.string()))));
}

} // namespace

0 comments on commit 68e6c0f

Please sign in to comment.