Added security checks for symlinks

Added security checks which prevents service from interacting with
symlinks. It is not possible to list/delete/read/write to symlinks.

Tested:
Added unit tests which confirm that:
- Symlinks cannot be removed
- Symlinks are not listed
- Symlinks cannot be overwritten
- Symlinks cannot be read

Signed-off-by: Krzysztof Grobelny <krzysztof.grobelny@intel.com>
Change-Id: I50d6d10dac81fd454e7e30520a7c47d5146be58c
diff --git a/src/persistent_json_storage.cpp b/src/persistent_json_storage.cpp
index 896c8a9..0dc66ad 100644
--- a/src/persistent_json_storage.cpp
+++ b/src/persistent_json_storage.cpp
@@ -5,6 +5,8 @@
 #include <fstream>
 #include <stdexcept>
 
+using namespace std::literals::string_literals;
+
 PersistentJsonStorage::PersistentJsonStorage(const DirectoryPath& directory) :
     directory(directory)
 {}
@@ -28,6 +30,8 @@
                 ", ec=" + std::to_string(ec.value()) + ": " + ec.message());
         }
 
+        assertThatPathIsNotSymlink(path);
+
         std::ofstream file(path);
         file << data;
         if (!file)
@@ -48,6 +52,12 @@
 bool PersistentJsonStorage::remove(const FilePath& filePath)
 {
     const auto path = join(directory, filePath);
+
+    if (std::filesystem::is_symlink(path))
+    {
+        return false;
+    }
+
     std::error_code ec;
 
     auto removed = std::filesystem::remove(path, ec);
@@ -79,6 +89,7 @@
 
     try
     {
+        assertThatPathIsNotSymlink(path);
         std::ifstream file(path);
         file >> result;
     }
@@ -103,7 +114,7 @@
     for (const auto& p :
          std::filesystem::recursive_directory_iterator(directory))
     {
-        if (p.is_regular_file())
+        if (p.is_regular_file() && !p.is_symlink())
         {
             auto item = std::filesystem::relative(p.path(), directory);
             result.emplace_back(std::move(item));
@@ -134,3 +145,14 @@
 {
     return std::filesystem::exists(join(directory, subPath));
 }
+
+void PersistentJsonStorage::assertThatPathIsNotSymlink(
+    const std::filesystem::path& path)
+{
+    if (std::filesystem::is_symlink(path))
+    {
+        throw std::runtime_error(
+            "Source/Target file is a symlink! Operation canceled on path "s +
+            path.c_str());
+    }
+}
diff --git a/src/persistent_json_storage.hpp b/src/persistent_json_storage.hpp
index 2b0f365..064a4c6 100644
--- a/src/persistent_json_storage.hpp
+++ b/src/persistent_json_storage.hpp
@@ -19,4 +19,5 @@
     static std::filesystem::path join(const std::filesystem::path&,
                                       const std::filesystem::path&);
     static void limitPermissions(const std::filesystem::path& path);
+    static void assertThatPathIsNotSymlink(const std::filesystem::path& path);
 };
diff --git a/tests/src/test_persistent_json_storage.cpp b/tests/src/test_persistent_json_storage.cpp
index 7ddc5b6..8e67a2e 100644
--- a/tests/src/test_persistent_json_storage.cpp
+++ b/tests/src/test_persistent_json_storage.cpp
@@ -1,6 +1,8 @@
 #include "helpers.hpp"
 #include "persistent_json_storage.hpp"
 
+#include <fstream>
+
 #include "gmock/gmock.h"
 #include "gtest/gtest.h"
 
@@ -111,3 +113,69 @@
 {
     ASSERT_THAT(sut.load(fileName), Eq(std::nullopt));
 }
+
+class TestPersistentJsonStorageWithSymlink : public TestPersistentJsonStorage
+{
+  public:
+    TestPersistentJsonStorageWithSymlink()
+    {
+        std::ofstream file(dummyReportPath);
+        file << "{}";
+        file.close();
+
+        std::filesystem::create_directories(std::filesystem::path(directory) /
+                                            "report");
+        std::filesystem::create_symlink(dummyReportPath,
+                                        std::filesystem::path(directory) /
+                                            "report/symlink.json");
+    }
+
+    static void SetUpTestSuite()
+    {
+        TestPersistentJsonStorage::SetUpTestSuite();
+        ASSERT_FALSE(std::filesystem::exists(dummyReportPath));
+    }
+
+    void TearDown() override
+    {
+        TestPersistentJsonStorage::TearDown();
+        if (std::filesystem::exists(dummyReportPath))
+        {
+            std::filesystem::remove(dummyReportPath);
+        }
+    }
+
+    static const std::filesystem::path dummyReportPath;
+};
+
+const std::filesystem::path
+    TestPersistentJsonStorageWithSymlink::dummyReportPath =
+        std::filesystem::temp_directory_path() / "report";
+
+TEST_F(TestPersistentJsonStorageWithSymlink, symlinksAreNotListed)
+{
+    ASSERT_THAT(sut.list(), UnorderedElementsAre());
+}
+
+TEST_F(TestPersistentJsonStorageWithSymlink, throwsWhenStoreTargetIsSymlink)
+{
+    ASSERT_THROW(
+        sut.store(FilePath("report/symlink.json"), nlohmann::json("data")),
+        std::runtime_error);
+
+    ASSERT_THAT(sut.list(), UnorderedElementsAre());
+}
+
+TEST_F(TestPersistentJsonStorageWithSymlink, returnsNulloptWhenFileIsSymlink)
+{
+    ASSERT_THAT(sut.load(FilePath("report/symlink.json")), Eq(std::nullopt));
+}
+
+TEST_F(TestPersistentJsonStorageWithSymlink,
+       returnsFalseWhenTryingToDeleteSymlink)
+{
+    EXPECT_THAT(sut.remove(FilePath("report/symlink.json")), Eq(false));
+    EXPECT_TRUE(std::filesystem::exists(std::filesystem::path(directory) /
+                                        "report/symlink.json"));
+    EXPECT_TRUE(std::filesystem::exists(dummyReportPath));
+}