| // 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 "biod/biod_storage.h" |
| |
| #include <algorithm> |
| #include <unordered_set> |
| #include <vector> |
| |
| #include <base/bind.h> |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include <base/files/scoped_temp_dir.h> |
| #include <testing/gtest/include/gtest/gtest.h> |
| |
| namespace biod { |
| |
| namespace { |
| |
| const char kBiometricsManagerName[] = "BiometricsManager"; |
| |
| const base::FilePath kFilePath("TestFile"); |
| |
| constexpr int kInvalidRecordFormatVersion = -1; |
| |
| const char kRecordId1[] = "00000000_0000_0000_0000_000000000001"; |
| const char kUserId1[] = "0000000000000000000000000000000000000001"; |
| const char kLabel1[] = "record1"; |
| const std::vector<uint8_t> kValidationVal1 = {0x00, 0x01}; |
| const char kData1[] = "Hello, world1!"; |
| |
| const char kRecordId2[] = "00000000_0000_0000_0000_000000000002"; |
| const char kUserId2[] = "0000000000000000000000000000000000000002"; |
| const char kLabel2[] = "record2"; |
| const std::vector<uint8_t> kValidationVal2 = {0x00, 0x02}; |
| const char kData2[] = "Hello, world2!"; |
| |
| const char kRecordId3[] = "00000000_0000_0000_0000_000000000003"; |
| const char kLabel3[] = "record3"; |
| const std::vector<uint8_t> kValidationVal3 = {0x00, 0x03}; |
| const char kData3[] = "Hello, world3!"; |
| |
| constexpr int kPermissions600 = |
| base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER; |
| constexpr int kPermissions700 = base::FILE_PERMISSION_USER_MASK; |
| |
| // Flag to control whether to run tests with positive match secret support. |
| // This can't be a member of the test fixture because it's accessed in the |
| // TestRecord class in anonymous namespace. |
| bool test_record_supports_positive_match_secret = true; |
| |
| class TestRecord : public BiometricsManager::Record { |
| public: |
| TestRecord(const std::string& id, |
| const std::string& user_id, |
| const std::string& label, |
| const std::vector<uint8_t>& validation_val, |
| const std::string& data) |
| : id_(id), |
| user_id_(user_id), |
| label_(label), |
| validation_val_(validation_val), |
| data_(data) {} |
| |
| // BiometricsManager::Record overrides: |
| const std::string& GetId() const override { return id_; } |
| const std::string& GetUserId() const override { return user_id_; } |
| const std::string& GetLabel() const override { return label_; } |
| const std::vector<uint8_t>& GetValidationVal() const override { |
| return validation_val_; |
| } |
| const std::string& GetData() const { return data_; } |
| bool SetLabel(std::string label) override { return true; } |
| bool Remove() override { return true; } |
| bool SupportsPositiveMatchSecret() const override { |
| return test_record_supports_positive_match_secret; |
| } |
| bool NeedsNewValidationValue() const override { return false; } |
| |
| void ClearValidationValue() { validation_val_.clear(); } |
| |
| friend bool operator==(const TestRecord& lhs, const TestRecord& rhs) { |
| return lhs.id_ == rhs.id_ && lhs.user_id_ == rhs.user_id_ && |
| lhs.validation_val_ == rhs.validation_val_ && |
| lhs.label_ == rhs.label_ && lhs.data_ == rhs.data_; |
| } |
| friend inline bool operator!=(const TestRecord& lhs, const TestRecord& rhs) { |
| return !(lhs == rhs); |
| } |
| |
| private: |
| std::string id_; |
| std::string user_id_; |
| std::string label_; |
| std::vector<uint8_t> validation_val_; |
| std::string data_; |
| }; |
| } // namespace |
| |
| class BiodStorageBaseTest : public ::testing::Test { |
| public: |
| BiodStorageBaseTest() { |
| CHECK(temp_dir_.CreateUniqueTempDir()); |
| root_path_ = temp_dir_.GetPath().AppendASCII("biod_storage_unittest_root"); |
| biod_storage_.reset(new BiodStorage( |
| kBiometricsManagerName, |
| base::Bind(&BiodStorageBaseTest::LoadRecord, base::Unretained(this)))); |
| // Since there is no session manager, allow accesses by default. |
| biod_storage_->set_allow_access(true); |
| biod_storage_->SetRootPathForTesting(root_path_); |
| } |
| |
| ~BiodStorageBaseTest() override { |
| EXPECT_TRUE(base::DeleteFile(temp_dir_.GetPath(), true)); |
| } |
| |
| base::DictionaryValue CreateRecordDictionary( |
| const std::vector<uint8_t>& validation_val) { |
| base::DictionaryValue record_dictionary; |
| std::string validation_value_str(validation_val.begin(), |
| validation_val.end()); |
| base::Base64Encode(validation_value_str, &validation_value_str); |
| record_dictionary.SetString("match_validation_value", validation_value_str); |
| return record_dictionary; |
| } |
| |
| protected: |
| base::ScopedTempDir temp_dir_; |
| base::FilePath root_path_; |
| std::unique_ptr<BiodStorage> biod_storage_; |
| std::vector<TestRecord> records_; |
| |
| private: |
| // LoadRecord is a callback passed to biod_storage_. It gets called when |
| // biod_storage_ reads a record from storage. It loads the new record into |
| // records_. |
| bool LoadRecord(int record_format_version, |
| const std::string& user_id, |
| const std::string& label, |
| const std::string& record_id, |
| const std::vector<uint8_t>& validation_val, |
| const base::Value& data_value) { |
| std::string data; |
| data_value.GetAsString(&data); |
| records_.push_back( |
| TestRecord(record_id, user_id, label, validation_val, data)); |
| return true; |
| } |
| |
| DISALLOW_COPY_AND_ASSIGN(BiodStorageBaseTest); |
| }; |
| |
| class BiodStorageTest : public BiodStorageBaseTest, |
| public ::testing::WithParamInterface<bool> { |
| public: |
| BiodStorageTest() { test_record_supports_positive_match_secret = GetParam(); } |
| }; |
| |
| TEST_P(BiodStorageTest, WriteAndReadRecords) { |
| const std::vector<uint8_t> kEmpty; |
| const std::vector<TestRecord> kRecords = { |
| TestRecord( |
| kRecordId1, kUserId1, kLabel1, |
| test_record_supports_positive_match_secret ? kValidationVal1 : kEmpty, |
| kData1), |
| TestRecord( |
| kRecordId2, kUserId2, kLabel2, |
| test_record_supports_positive_match_secret ? kValidationVal2 : kEmpty, |
| kData2), |
| TestRecord( |
| kRecordId3, kUserId2, kLabel3, |
| test_record_supports_positive_match_secret ? kValidationVal3 : kEmpty, |
| kData3)}; |
| |
| // Write the record. |
| for (auto const& record : kRecords) { |
| EXPECT_TRUE(biod_storage_->WriteRecord( |
| record, std::make_unique<base::Value>(record.GetData()))); |
| } |
| |
| // Read the record. |
| std::unordered_set<std::string> user_ids({kUserId1, kUserId2}); |
| EXPECT_TRUE(biod_storage_->ReadRecords(user_ids)); |
| EXPECT_TRUE( |
| std::is_permutation(kRecords.begin(), kRecords.end(), records_.begin())); |
| } |
| |
| TEST_F(BiodStorageBaseTest, WriteRecord_CheckUmask) { |
| auto record = |
| TestRecord(kRecordId1, kUserId1, kLabel1, kValidationVal1, kData1); |
| |
| const base::FilePath kRecordStorageFilename = |
| root_path_.Append("biod") |
| .Append(record.GetUserId()) |
| .Append(kBiometricsManagerName) |
| .Append("Record" + record.GetId()); |
| |
| ASSERT_FALSE(base::PathExists(kRecordStorageFilename)); |
| ASSERT_FALSE(base::PathExists(kRecordStorageFilename.DirName())); |
| |
| EXPECT_TRUE(biod_storage_->WriteRecord( |
| record, std::make_unique<base::Value>(record.GetData()))); |
| |
| // Check permissions of directory |
| int actual_permissions; |
| EXPECT_TRUE(base::GetPosixFilePermissions(kRecordStorageFilename.DirName(), |
| &actual_permissions)); |
| EXPECT_EQ(kPermissions700, actual_permissions); |
| |
| // Check permissions of record |
| EXPECT_TRUE(base::GetPosixFilePermissions(kRecordStorageFilename, |
| &actual_permissions)); |
| EXPECT_EQ(kPermissions600, actual_permissions); |
| } |
| |
| TEST_P(BiodStorageTest, DeleteRecord) { |
| const std::vector<uint8_t> kEmpty; |
| const TestRecord kRecord( |
| kRecordId1, kUserId1, kLabel1, |
| test_record_supports_positive_match_secret ? kValidationVal1 : kEmpty, |
| kData1); |
| |
| // Delete a non-existent record. |
| EXPECT_TRUE(biod_storage_->DeleteRecord(kUserId1, kRecordId1)); |
| |
| EXPECT_TRUE(biod_storage_->WriteRecord( |
| kRecord, std::make_unique<base::Value>(kRecord.GetData()))); |
| |
| // Check this record is properly written. |
| std::unordered_set<std::string> user_ids({kUserId1}); |
| EXPECT_TRUE(biod_storage_->ReadRecords(user_ids)); |
| ASSERT_FALSE(records_.empty()); |
| ASSERT_EQ(1u, records_.size()); |
| EXPECT_EQ(records_[0], kRecord); |
| |
| records_.clear(); |
| |
| EXPECT_TRUE(biod_storage_->DeleteRecord(kUserId1, kRecordId1)); |
| |
| // Check this record is properly deleted. |
| EXPECT_TRUE(biod_storage_->ReadRecords(user_ids)); |
| EXPECT_TRUE(records_.empty()); |
| } |
| |
| TEST_F(BiodStorageBaseTest, GenerateNewRecordId) { |
| // Check the two record ids are different. |
| std::string record_id1(biod_storage_->GenerateNewRecordId()); |
| std::string record_id2(biod_storage_->GenerateNewRecordId()); |
| EXPECT_NE(record_id1, record_id2); |
| } |
| |
| TEST_F(BiodStorageBaseTest, TestEqualOperator) { |
| EXPECT_EQ(TestRecord(kRecordId1, kUserId1, kLabel1, kValidationVal1, kData1), |
| TestRecord(kRecordId1, kUserId1, kLabel1, kValidationVal1, kData1)); |
| |
| EXPECT_NE(TestRecord(kRecordId1, kUserId1, kLabel1, kValidationVal1, kData1), |
| TestRecord(kRecordId1, kUserId1, kLabel1, kValidationVal2, kData1)); |
| } |
| |
| TEST_F(BiodStorageBaseTest, TestReadValidationValueFromRecord) { |
| auto record_dictionary = CreateRecordDictionary(kValidationVal1); |
| auto ret = biod_storage_->ReadValidationValueFromRecord( |
| kRecordFormatVersion, &record_dictionary, kFilePath); |
| EXPECT_TRUE(ret != nullptr); |
| EXPECT_EQ(*ret, kValidationVal1); |
| } |
| |
| TEST_F(BiodStorageBaseTest, TestReadValidationValueFromRecordOldVersion) { |
| auto record_dictionary = CreateRecordDictionary(kValidationVal1); |
| std::vector<uint8_t> empty; |
| auto ret = biod_storage_->ReadValidationValueFromRecord( |
| kRecordFormatVersionNoValidationValue, &record_dictionary, kFilePath); |
| EXPECT_TRUE(ret != nullptr); |
| EXPECT_EQ(*ret, empty); |
| } |
| |
| TEST_F(BiodStorageBaseTest, TestReadValidationValueFromRecordInvalidVersion) { |
| auto record_dictionary = CreateRecordDictionary(kValidationVal1); |
| std::vector<uint8_t> empty; |
| auto ret = biod_storage_->ReadValidationValueFromRecord( |
| kInvalidRecordFormatVersion, &record_dictionary, kFilePath); |
| EXPECT_EQ(ret, nullptr); |
| } |
| |
| INSTANTIATE_TEST_CASE_P(RecordsSupportPositiveMatchSecret, |
| BiodStorageTest, |
| ::testing::Values(true, false)); |
| } // namespace biod |