diff options
author | Vitaly Buka <vitalybuka@google.com> | 2016-01-21 15:09:34 -0800 |
---|---|---|
committer | Vitaly Buka <vitalybuka@google.com> | 2016-01-21 23:53:01 +0000 |
commit | 5a7c4f55a0bdbd9c9e443d5b59414d1705919626 (patch) | |
tree | 7cd9fbac4a1bf795df47a2f4f9d1155803f4bf9b | |
parent | 484b6e4b808eb57f02253fbf63f117e259471c39 (diff) | |
download | libweave-5a7c4f55a0bdbd9c9e443d5b59414d1705919626.tar.gz |
Add black list manager implementation
BUG:25776798
BUG:25777806
BUG:25777658
BUG:25777810
Change-Id: I86ec09e727360c1de4ae9af63f2ff1ab5f9cea1c
Reviewed-on: https://weave-review.googlesource.com/2276
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
-rw-r--r-- | file_lists.mk | 2 | ||||
-rw-r--r-- | src/access_black_list_manager_impl.cc | 164 | ||||
-rw-r--r-- | src/access_black_list_manager_impl.h | 58 | ||||
-rw-r--r-- | src/access_black_list_manager_impl_unittest.cc | 167 | ||||
-rw-r--r-- | src/device_manager.cc | 6 | ||||
-rw-r--r-- | src/device_manager.h | 4 |
6 files changed, 401 insertions, 0 deletions
diff --git a/file_lists.mk b/file_lists.mk index 6356b2b..8dccd9c 100644 --- a/file_lists.mk +++ b/file_lists.mk @@ -4,6 +4,7 @@ WEAVE_SRC_FILES := \ src/access_api_handler.cc \ + src/access_black_list_manager_impl.cc \ src/backoff_entry.cc \ src/base_api_handler.cc \ src/commands/cloud_command_proxy.cc \ @@ -50,6 +51,7 @@ WEAVE_TEST_SRC_FILES := \ WEAVE_UNITTEST_SRC_FILES := \ src/access_api_handler_unittest.cc \ + src/access_black_list_manager_impl_unittest.cc \ src/backoff_entry_unittest.cc \ src/base_api_handler_unittest.cc \ src/commands/cloud_command_proxy_unittest.cc \ diff --git a/src/access_black_list_manager_impl.cc b/src/access_black_list_manager_impl.cc new file mode 100644 index 0000000..e6897ba --- /dev/null +++ b/src/access_black_list_manager_impl.cc @@ -0,0 +1,164 @@ +// Copyright 2016 The Weave 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 "src/access_black_list_manager_impl.h" + +#include <base/json/json_reader.h> +#include <base/json/json_writer.h> +#include <base/values.h> + +#include "src/commands/schema_constants.h" +#include "src/data_encoding.h" + +namespace weave { + +namespace { +const char kConfigFileName[] = "black_list"; + +const char kUser[] = "user"; +const char kApp[] = "app"; +const char kExpiration[] = "expiration"; +} + +AccessBlackListManagerImpl::AccessBlackListManagerImpl( + provider::ConfigStore* store, + size_t capacity, + base::Clock* clock) + : capacity_{capacity}, clock_{clock}, store_{store} { + Load(); +} + +void AccessBlackListManagerImpl::Load() { + if (!store_) + return; + if (auto list = base::ListValue::From( + base::JSONReader::Read(store_->LoadSettings(kConfigFileName)))) { + for (const auto& e : *list) { + const base::DictionaryValue* entry{nullptr}; + std::string user; + std::string app; + decltype(entries_)::key_type key; + int expiration; + if (e->GetAsDictionary(&entry) && entry->GetString(kUser, &user) && + Base64Decode(user, &key.first) && entry->GetString(kApp, &app) && + Base64Decode(app, &key.second) && + entry->GetInteger(kExpiration, &expiration)) { + base::Time expiration_time = base::Time::FromTimeT(expiration); + if (expiration_time > clock_->Now()) + entries_[key] = expiration_time; + } + } + if (entries_.size() < list->GetSize()) { + // Save some storage space by saving without expired entries. + Save({}); + } + } +} + +void AccessBlackListManagerImpl::Save(const DoneCallback& callback) { + if (!store_) { + if (!callback.is_null()) + callback.Run(nullptr); + return; + } + + base::ListValue list; + for (const auto& e : entries_) { + scoped_ptr<base::DictionaryValue> entry{new base::DictionaryValue}; + entry->SetString(kUser, Base64Encode(e.first.first)); + entry->SetString(kApp, Base64Encode(e.first.second)); + entry->SetInteger(kExpiration, e.second.ToTimeT()); + list.Append(std::move(entry)); + } + + std::string json; + base::JSONWriter::Write(list, &json); + store_->SaveSettings(kConfigFileName, json, callback); +} + +void AccessBlackListManagerImpl::RemoveExpired() { + for (auto i = begin(entries_); i != end(entries_);) { + if (i->second <= clock_->Now()) + i = entries_.erase(i); + else + ++i; + } +} + +void AccessBlackListManagerImpl::Block(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const base::Time& expiration, + const DoneCallback& callback) { + // Iterating is OK as Save below is more expensive. + RemoveExpired(); + if (expiration <= clock_->Now()) { + if (!callback.is_null()) { + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, + "aleady_expired", "Entry already expired"); + callback.Run(std::move(error)); + } + return; + } + if (entries_.size() >= capacity_) { + if (!callback.is_null()) { + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, + "blacklist_is_full", "Unable to store more entries"); + callback.Run(std::move(error)); + } + return; + } + auto& value = entries_[std::make_pair(user_id, app_id)]; + value = std::max(value, expiration); + Save(callback); +} + +void AccessBlackListManagerImpl::Unblock(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const DoneCallback& callback) { + if (!entries_.erase(std::make_pair(user_id, app_id))) { + if (!callback.is_null()) { + ErrorPtr error; + Error::AddTo(&error, FROM_HERE, errors::commands::kDomain, + "entry_not_found", "Unknown entry"); + callback.Run(std::move(error)); + } + return; + } + // Iterating is OK as Save below is more expensive. + RemoveExpired(); + Save(callback); +} + +bool AccessBlackListManagerImpl::IsBlocked( + const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id) const { + for (const auto& user : {{}, user_id}) { + for (const auto& app : {{}, app_id}) { + auto both = entries_.find(std::make_pair(user, app)); + if (both != end(entries_) && both->second > clock_->Now()) + return true; + } + } + return false; +} + +std::vector<AccessBlackListManager::Entry> +AccessBlackListManagerImpl::GetEntries() const { + std::vector<Entry> result; + for (const auto& e : entries_) + result.push_back({e.first.first, e.first.second, e.second}); + return result; +} + +size_t AccessBlackListManagerImpl::GetSize() const { + return entries_.size(); +} + +size_t AccessBlackListManagerImpl::GetCapacity() const { + return capacity_; +} + +} // namespace weave diff --git a/src/access_black_list_manager_impl.h b/src/access_black_list_manager_impl.h new file mode 100644 index 0000000..1c175db --- /dev/null +++ b/src/access_black_list_manager_impl.h @@ -0,0 +1,58 @@ +// Copyright 2016 The Weave Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +#ifndef LIBWEAVE_SRC_ACCESS_BLACK_LIST_IMPL_H_ +#define LIBWEAVE_SRC_ACCESS_BLACK_LIST_IMPL_H_ + +#include <map> +#include <utility> + +#include <base/time/default_clock.h> +#include <base/time/time.h> +#include <weave/error.h> +#include <weave/provider/config_store.h> + +#include "src/access_black_list_manager.h" + +namespace weave { + +class AccessBlackListManagerImpl : public AccessBlackListManager { + public: + explicit AccessBlackListManagerImpl(provider::ConfigStore* store, + size_t capacity = 1024, + base::Clock* clock = nullptr); + + // AccessBlackListManager implementation. + void Block(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const base::Time& expiration, + const DoneCallback& callback) override; + void Unblock(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id, + const DoneCallback& callback) override; + bool IsBlocked(const std::vector<uint8_t>& user_id, + const std::vector<uint8_t>& app_id) const override; + std::vector<Entry> GetEntries() const override; + size_t GetSize() const override; + size_t GetCapacity() const override; + + private: + void Load(); + void Save(const DoneCallback& callback); + void RemoveExpired(); + + const size_t capacity_{0}; + base::DefaultClock default_clock_; + base::Clock* clock_{&default_clock_}; + + provider::ConfigStore* store_{nullptr}; + std::map<std::pair<std::vector<uint8_t>, std::vector<uint8_t>>, base::Time> + entries_; + + DISALLOW_COPY_AND_ASSIGN(AccessBlackListManagerImpl); +}; + +} // namespace weave + +#endif // LIBWEAVE_SRC_ACCESS_BLACK_LIST_IMPL_H_ diff --git a/src/access_black_list_manager_impl_unittest.cc b/src/access_black_list_manager_impl_unittest.cc new file mode 100644 index 0000000..2b6d66e --- /dev/null +++ b/src/access_black_list_manager_impl_unittest.cc @@ -0,0 +1,167 @@ +// Copyright 2016 The Weave 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 "src/access_black_list_manager_impl.h" + +#include <gmock/gmock.h> +#include <gtest/gtest.h> +#include <weave/provider/test/mock_config_store.h> +#include <weave/test/unittest_utils.h> + +#include "src/test/mock_clock.h" +#include "src/bind_lambda.h" + +using testing::_; +using testing::Return; +using testing::StrictMock; + +namespace weave { + +class AccessBlackListManagerImplTest : public testing::Test { + protected: + void SetUp() { + std::string to_load = R"([{ + "user": "BQID", + "app": "BwQF", + "expiration": 1410000000 + }, { + "user": "AQID", + "app": "AwQF", + "expiration": 1419999999 + }])"; + + EXPECT_CALL(config_store_, LoadSettings("black_list")) + .WillOnce(Return(to_load)); + + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + std::string to_save = R"([{ + "user": "AQID", + "app": "AwQF", + "expiration": 1419999999 + }])"; + EXPECT_JSON_EQ(to_save, *test::CreateValue(json)); + if (!callback.is_null()) + callback.Run(nullptr); + }))); + + EXPECT_CALL(clock_, Now()) + .WillRepeatedly(Return(base::Time::FromTimeT(1412121212))); + manager_.reset(new AccessBlackListManagerImpl{&config_store_, 10, &clock_}); + } + StrictMock<test::MockClock> clock_; + StrictMock<provider::test::MockConfigStore> config_store_{false}; + std::unique_ptr<AccessBlackListManagerImpl> manager_; +}; + +TEST_F(AccessBlackListManagerImplTest, Init) { + EXPECT_EQ(1u, manager_->GetSize()); + EXPECT_EQ(10u, manager_->GetCapacity()); + EXPECT_EQ((std::vector<AccessBlackListManagerImpl::Entry>{{ + {1, 2, 3}, {3, 4, 5}, base::Time::FromTimeT(1419999999), + }}), + manager_->GetEntries()); +} + +TEST_F(AccessBlackListManagerImplTest, Block) { + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + std::string to_save = R"([{ + "user": "AQID", + "app": "AwQF", + "expiration": 1419999999 + }, { + "app": "CAgI", + "user": "BwcH", + "expiration": 1419990000 + }])"; + EXPECT_JSON_EQ(to_save, *test::CreateValue(json)); + if (!callback.is_null()) + callback.Run(nullptr); + }))); + manager_->Block({7, 7, 7}, {8, 8, 8}, base::Time::FromTimeT(1419990000), {}); +} + +TEST_F(AccessBlackListManagerImplTest, BlockExpired) { + manager_->Block( + {}, {}, base::Time::FromTimeT(1400000000), base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("command_schema", "aleady_expired")); + })); +} + +TEST_F(AccessBlackListManagerImplTest, BlockListIsFull) { + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillRepeatedly(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + if (!callback.is_null()) + callback.Run(nullptr); + }))); + for (size_t i = manager_->GetSize(); i < manager_->GetCapacity(); ++i) { + manager_->Block( + {99, static_cast<uint8_t>(i / 256), static_cast<uint8_t>(i % 256)}, + {8, 8, 8}, base::Time::FromTimeT(1419990000), {}); + EXPECT_EQ(i + 1, manager_->GetSize()); + } + manager_->Block( + {99}, {8, 8, 8}, base::Time::FromTimeT(1419990000), + base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("command_schema", "blacklist_is_full")); + })); +} + +TEST_F(AccessBlackListManagerImplTest, Unblock) { + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<1, 2>(testing::Invoke( + [](const std::string& json, const DoneCallback& callback) { + EXPECT_JSON_EQ("[]", *test::CreateValue(json)); + if (!callback.is_null()) + callback.Run(nullptr); + }))); + manager_->Unblock({1, 2, 3}, {3, 4, 5}, {}); +} + +TEST_F(AccessBlackListManagerImplTest, UnblockNotFound) { + manager_->Unblock( + {5, 2, 3}, {5, 4, 5}, base::Bind([](ErrorPtr error) { + EXPECT_TRUE(error->HasError("command_schema", "entry_not_found")); + })); +} + +TEST_F(AccessBlackListManagerImplTest, IsBlockedFalse) { + EXPECT_FALSE(manager_->IsBlocked({7, 7, 7}, {8, 8, 8})); +} + +class AccessBlackListManagerImplIsBlockedTest + : public AccessBlackListManagerImplTest, + public testing::WithParamInterface< + std::tuple<std::vector<uint8_t>, std::vector<uint8_t>>> { + public: + void SetUp() override { + AccessBlackListManagerImplTest::SetUp(); + EXPECT_CALL(config_store_, SaveSettings("black_list", _, _)) + .WillOnce(testing::WithArgs<2>( + testing::Invoke([](const DoneCallback& callback) { + if (!callback.is_null()) + callback.Run(nullptr); + }))); + manager_->Block(std::get<0>(GetParam()), std::get<1>(GetParam()), + base::Time::FromTimeT(1419990000), {}); + } +}; + +TEST_P(AccessBlackListManagerImplIsBlockedTest, IsBlocked) { + EXPECT_TRUE(manager_->IsBlocked({7, 7, 7}, {8, 8, 8})); +} + +INSTANTIATE_TEST_CASE_P( + Filters, + AccessBlackListManagerImplIsBlockedTest, + testing::Combine(testing::Values(std::vector<uint8_t>{}, + std::vector<uint8_t>{7, 7, 7}), + testing::Values(std::vector<uint8_t>{}, + std::vector<uint8_t>{8, 8, 8}))); + +} // namespace weave diff --git a/src/device_manager.cc b/src/device_manager.cc index 1158df7..50818ff 100644 --- a/src/device_manager.cc +++ b/src/device_manager.cc @@ -8,6 +8,8 @@ #include <base/bind.h> +#include "src/access_api_handler.h" +#include "src/access_black_list_manager_impl.h" #include "src/base_api_handler.h" #include "src/commands/schema_constants.h" #include "src/component_manager_impl.h" @@ -40,6 +42,10 @@ DeviceManager::DeviceManager(provider::ConfigStore* config_store, network, auth_manager_.get())); base_api_handler_.reset(new BaseApiHandler{device_info_.get(), this}); + black_list_manager_.reset(new AccessBlackListManagerImpl{config_store}); + access_api_handler_.reset( + new AccessApiHandler{this, black_list_manager_.get()}); + device_info_->Start(); if (http_server) { diff --git a/src/device_manager.h b/src/device_manager.h index d40ba8e..d77bacc 100644 --- a/src/device_manager.h +++ b/src/device_manager.h @@ -10,6 +10,8 @@ namespace weave { +class AccessApiHandler; +class AccessBlackListManager; class BaseApiHandler; class Config; class ComponentManager; @@ -107,6 +109,8 @@ class DeviceManager final : public Device { std::unique_ptr<ComponentManager> component_manager_; std::unique_ptr<DeviceRegistrationInfo> device_info_; std::unique_ptr<BaseApiHandler> base_api_handler_; + std::unique_ptr<AccessBlackListManager> black_list_manager_; + std::unique_ptr<AccessApiHandler> access_api_handler_; std::unique_ptr<privet::Manager> privet_; base::WeakPtrFactory<DeviceManager> weak_ptr_factory_{this}; |