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>
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_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 @@
       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 @@
   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};