Implemented _accessControlBlackList trait

BUG:25768272
BUG:25777665
Change-Id: Id32475816dbd9ec24b020f4f2a68274a978729c8
Reviewed-on: https://weave-review.googlesource.com/2209
Reviewed-by: Vitaly Buka <vitalybuka@google.com>
diff --git a/file_lists.mk b/file_lists.mk
index 1e78935..e79372e 100644
--- a/file_lists.mk
+++ b/file_lists.mk
@@ -3,6 +3,7 @@
 # found in the LICENSE file.
 
 WEAVE_SRC_FILES := \
+	src/access_api_handler.cc \
 	src/backoff_entry.cc \
 	src/base_api_handler.cc \
 	src/commands/cloud_command_proxy.cc \
@@ -48,6 +49,7 @@
 	src/test/unittest_utils.cc
 
 WEAVE_UNITTEST_SRC_FILES := \
+	src/access_api_handler_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_api_handler.cc b/src/access_api_handler.cc
new file mode 100644
index 0000000..e03a548
--- /dev/null
+++ b/src/access_api_handler.cc
@@ -0,0 +1,233 @@
+// 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_api_handler.h"
+
+#include <base/bind.h>
+#include <weave/device.h>
+
+#include "src/access_black_list_manager.h"
+#include "src/commands/schema_constants.h"
+#include "src/data_encoding.h"
+#include "src/json_error_codes.h"
+
+namespace weave {
+
+namespace {
+
+const char kComponent[] = "accessControl";
+const char kTrait[] = "_accessControlBlackList";
+const char kStateSize[] = "_accessControlBlackList.size";
+const char kStateCapacity[] = "_accessControlBlackList.capacity";
+const char kUserId[] = "userId";
+const char kApplicationId[] = "applicationId";
+const char kExpirationTimeout[] = "expirationTimeoutSec";
+const char kBlackList[] = "blackList";
+
+bool GetIds(const base::DictionaryValue& parameters,
+            std::vector<uint8_t>* user_id_decoded,
+            std::vector<uint8_t>* app_id_decoded,
+            ErrorPtr* error) {
+  std::string user_id;
+  parameters.GetString(kUserId, &user_id);
+  if (!Base64Decode(user_id, user_id_decoded)) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kInvalidPropValue,
+                       "Invalid user id '%s'", user_id.c_str());
+    return false;
+  }
+
+  std::string app_id;
+  parameters.GetString(kApplicationId, &app_id);
+  if (!Base64Decode(app_id, app_id_decoded)) {
+    Error::AddToPrintf(error, FROM_HERE, errors::commands::kDomain,
+                       errors::commands::kInvalidPropValue,
+                       "Invalid app id '%s'", user_id.c_str());
+    return false;
+  }
+
+  return true;
+}
+
+}  // namespace
+
+AccessApiHandler::AccessApiHandler(Device* device,
+                                   AccessBlackListManager* manager)
+    : device_{device}, manager_{manager} {
+  device_->AddTraitDefinitionsFromJson(R"({
+    "_accessControlBlackList": {
+      "commands": {
+        "block": {
+          "minimalRole": "owner",
+          "parameters": {
+            "userId": {
+              "type": "string"
+            },
+            "applicationId": {
+              "type": "string"
+            },
+            "expirationTimeoutSec": {
+              "type": "integer"
+            }
+          }
+        },
+        "unblock": {
+          "minimalRole": "owner",
+          "parameters": {
+            "userId": {
+              "type": "string"
+            },
+            "applicationId": {
+              "type": "string"
+            }
+          }
+        },
+        "list": {
+          "minimalRole": "owner",
+          "parameters": {},
+          "results": {
+            "blackList": {
+              "type": "array",
+              "items": {
+                "type": "object",
+                "properties": {
+                  "userId": {
+                    "type": "string"
+                  },
+                  "applicationId": {
+                    "type": "string"
+                  }
+                },
+                "additionalProperties": false
+              }
+            }
+          }
+        }
+      },
+      "state": {
+        "size": {
+          "type": "integer",
+          "isRequired": true
+        },
+        "capacity": {
+          "type": "integer",
+          "isRequired": true
+        }
+      }
+    }
+  })");
+  CHECK(device_->AddComponent(kComponent, {kTrait}, nullptr));
+  UpdateState();
+
+  device_->AddCommandHandler(
+      kComponent, "_accessControlBlackList.block",
+      base::Bind(&AccessApiHandler::Block, weak_ptr_factory_.GetWeakPtr()));
+  device_->AddCommandHandler(
+      kComponent, "_accessControlBlackList.unblock",
+      base::Bind(&AccessApiHandler::Unblock, weak_ptr_factory_.GetWeakPtr()));
+  device_->AddCommandHandler(
+      kComponent, "_accessControlBlackList.list",
+      base::Bind(&AccessApiHandler::List, weak_ptr_factory_.GetWeakPtr()));
+}
+
+void AccessApiHandler::Block(const std::weak_ptr<Command>& cmd) {
+  auto command = cmd.lock();
+  if (!command)
+    return;
+
+  CHECK(command->GetState() == Command::State::kQueued)
+      << EnumToString(command->GetState());
+  command->SetProgress(base::DictionaryValue{}, nullptr);
+
+  const auto& parameters = command->GetParameters();
+  std::vector<uint8_t> user_id;
+  std::vector<uint8_t> app_id;
+  ErrorPtr error;
+  if (!GetIds(parameters, &user_id, &app_id, &error)) {
+    command->Abort(error.get(), nullptr);
+    return;
+  }
+
+  int timeout_sec = 0;
+  parameters.GetInteger(kExpirationTimeout, &timeout_sec);
+
+  base::Time expiration =
+      base::Time::Now() + base::TimeDelta::FromSeconds(timeout_sec);
+
+  manager_->Block(user_id, app_id, expiration,
+                  base::Bind(&AccessApiHandler::OnCommandDone,
+                             weak_ptr_factory_.GetWeakPtr(), cmd));
+}
+
+void AccessApiHandler::Unblock(const std::weak_ptr<Command>& cmd) {
+  auto command = cmd.lock();
+  if (!command)
+    return;
+
+  CHECK(command->GetState() == Command::State::kQueued)
+      << EnumToString(command->GetState());
+  command->SetProgress(base::DictionaryValue{}, nullptr);
+
+  const auto& parameters = command->GetParameters();
+  std::vector<uint8_t> user_id;
+  std::vector<uint8_t> app_id;
+  ErrorPtr error;
+  if (!GetIds(parameters, &user_id, &app_id, &error)) {
+    command->Abort(error.get(), nullptr);
+    return;
+  }
+
+  if (!manager_->Unblock(user_id, app_id, &error)) {
+    command->Abort(error.get(), nullptr);
+    return;
+  }
+
+  UpdateState();
+  command->Complete({}, nullptr);
+}
+
+void AccessApiHandler::List(const std::weak_ptr<Command>& cmd) {
+  auto command = cmd.lock();
+  if (!command)
+    return;
+
+  CHECK(command->GetState() == Command::State::kQueued)
+      << EnumToString(command->GetState());
+  command->SetProgress(base::DictionaryValue{}, nullptr);
+
+  std::unique_ptr<base::ListValue> entries{new base::ListValue};
+  for (const auto& e : manager_->GetEntries()) {
+    std::unique_ptr<base::DictionaryValue> entry{new base::DictionaryValue};
+    entry->SetString(kUserId, Base64Encode(e.user_id));
+    entry->SetString(kApplicationId, Base64Encode(e.app_id));
+    entries->Append(entry.release());
+  }
+
+  base::DictionaryValue result;
+  result.Set(kBlackList, entries.release());
+
+  command->Complete(result, nullptr);
+}
+
+void AccessApiHandler::OnCommandDone(const std::weak_ptr<Command>& cmd,
+                                     ErrorPtr error) {
+  auto command = cmd.lock();
+  if (!command)
+    return;
+  UpdateState();
+  if (error) {
+    command->Abort(error.get(), nullptr);
+    return;
+  }
+  command->Complete({}, nullptr);
+}
+
+void AccessApiHandler::UpdateState() {
+  base::DictionaryValue state;
+  state.SetInteger(kStateSize, manager_->GetSize());
+  state.SetInteger(kStateCapacity, manager_->GetCapacity());
+  device_->SetStateProperties(kComponent, state, nullptr);
+}
+
+}  // namespace weave
diff --git a/src/access_api_handler.h b/src/access_api_handler.h
new file mode 100644
index 0000000..821ce02
--- /dev/null
+++ b/src/access_api_handler.h
@@ -0,0 +1,47 @@
+// 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_API_HANDLER_H_
+#define LIBWEAVE_SRC_ACCESS_API_HANDLER_H_
+
+#include <memory>
+
+#include <base/memory/weak_ptr.h>
+#include <weave/error.h>
+
+namespace weave {
+
+class AccessBlackListManager;
+class Command;
+class Device;
+
+// Handles commands for 'accessControlBlackList' trait.
+// Objects of the class subscribe for notification from CommandManager and
+// execute incoming commands.
+// Handled commands:
+//  accessControlBlackList.block
+//  accessControlBlackList.unblock
+//  accessControlBlackList.list
+class AccessApiHandler final {
+ public:
+  AccessApiHandler(Device* device, AccessBlackListManager* manager);
+
+ private:
+  void Block(const std::weak_ptr<Command>& command);
+  void Unblock(const std::weak_ptr<Command>& command);
+  void List(const std::weak_ptr<Command>& command);
+  void UpdateState();
+
+  void OnCommandDone(const std::weak_ptr<Command>& command, ErrorPtr error);
+
+  Device* device_{nullptr};
+  AccessBlackListManager* manager_{nullptr};
+
+  base::WeakPtrFactory<AccessApiHandler> weak_ptr_factory_{this};
+  DISALLOW_COPY_AND_ASSIGN(AccessApiHandler);
+};
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_ACCESS_API_HANDLER_H_
diff --git a/src/access_api_handler_unittest.cc b/src/access_api_handler_unittest.cc
new file mode 100644
index 0000000..3858722
--- /dev/null
+++ b/src/access_api_handler_unittest.cc
@@ -0,0 +1,249 @@
+// 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_api_handler.h"
+
+#include <gtest/gtest.h>
+#include <weave/test/mock_device.h>
+#include <weave/test/unittest_utils.h>
+
+#include "src/component_manager_impl.h"
+#include "src/access_black_list_manager.h"
+#include "src/data_encoding.h"
+
+using testing::_;
+using testing::AnyOf;
+using testing::Invoke;
+using testing::Return;
+using testing::StrictMock;
+using testing::WithArgs;
+
+namespace weave {
+
+class MockAccessBlackListManager : public AccessBlackListManager {
+ public:
+  MOCK_METHOD4(Block,
+               void(const std::vector<uint8_t>&,
+                    const std::vector<uint8_t>&,
+                    const base::Time&,
+                    const DoneCallback&));
+  MOCK_METHOD3(Unblock,
+               bool(const std::vector<uint8_t>&,
+                    const std::vector<uint8_t>&,
+                    ErrorPtr*));
+  MOCK_CONST_METHOD0(GetEntries, std::vector<Entry>());
+  MOCK_CONST_METHOD0(GetSize, size_t());
+  MOCK_CONST_METHOD0(GetCapacity, size_t());
+};
+
+class AccessApiHandlerTest : public ::testing::Test {
+ protected:
+  void SetUp() override {
+    EXPECT_CALL(device_, AddTraitDefinitionsFromJson(_))
+        .WillRepeatedly(Invoke([this](const std::string& json) {
+          EXPECT_TRUE(component_manager_.LoadTraits(json, nullptr));
+        }));
+    EXPECT_CALL(device_, SetStateProperties(_, _, _))
+        .WillRepeatedly(
+            Invoke(&component_manager_, &ComponentManager::SetStateProperties));
+    EXPECT_CALL(device_, SetStateProperty(_, _, _, _))
+        .WillRepeatedly(
+            Invoke(&component_manager_, &ComponentManager::SetStateProperty));
+    EXPECT_CALL(device_, AddComponent(_, _, _))
+        .WillRepeatedly(Invoke([this](const std::string& name,
+                                      const std::vector<std::string>& traits,
+                                      ErrorPtr* error) {
+          return component_manager_.AddComponent("", name, traits, error);
+        }));
+
+    EXPECT_CALL(device_,
+                AddCommandHandler(_, AnyOf("_accessControlBlackList.block",
+                                           "_accessControlBlackList.unblock",
+                                           "_accessControlBlackList.list"),
+                                  _))
+        .WillRepeatedly(
+            Invoke(&component_manager_, &ComponentManager::AddCommandHandler));
+
+    EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(0));
+
+    EXPECT_CALL(access_manager_, GetCapacity()).WillRepeatedly(Return(10));
+
+    handler_.reset(new AccessApiHandler{&device_, &access_manager_});
+  }
+
+  const base::DictionaryValue& AddCommand(const std::string& command) {
+    std::string id;
+    auto command_instance = component_manager_.ParseCommandInstance(
+        *test::CreateDictionaryValue(command.c_str()), Command::Origin::kLocal,
+        UserRole::kOwner, &id, nullptr);
+    EXPECT_NE(nullptr, command_instance.get());
+    component_manager_.AddCommand(std::move(command_instance));
+    EXPECT_EQ(Command::State::kDone,
+              component_manager_.FindCommand(id)->GetState());
+    return component_manager_.FindCommand(id)->GetResults();
+  }
+
+  std::unique_ptr<base::DictionaryValue> GetState() {
+    std::string path =
+        component_manager_.FindComponentWithTrait("_accessControlBlackList");
+    EXPECT_FALSE(path.empty());
+    const auto* component = component_manager_.FindComponent(path, nullptr);
+    EXPECT_TRUE(component);
+    const base::DictionaryValue* state = nullptr;
+    EXPECT_TRUE(
+        component->GetDictionary("state._accessControlBlackList", &state));
+    return std::unique_ptr<base::DictionaryValue>{state->DeepCopy()};
+  }
+
+  ComponentManagerImpl component_manager_;
+  StrictMock<test::MockDevice> device_;
+  StrictMock<MockAccessBlackListManager> access_manager_;
+  std::unique_ptr<AccessApiHandler> handler_;
+};
+
+TEST_F(AccessApiHandlerTest, Initialization) {
+  const base::DictionaryValue* trait = nullptr;
+  ASSERT_TRUE(component_manager_.GetTraits().GetDictionary(
+      "_accessControlBlackList", &trait));
+
+  auto expected = R"({
+    "commands": {
+      "block": {
+        "minimalRole": "owner",
+        "parameters": {
+          "userId": {
+            "type": "string"
+          },
+          "applicationId": {
+            "type": "string"
+          },
+          "expirationTimeoutSec": {
+            "type": "integer"
+          }
+        }
+      },
+      "unblock": {
+        "minimalRole": "owner",
+        "parameters": {
+          "userId": {
+            "type": "string"
+          },
+          "applicationId": {
+            "type": "string"
+          }
+        }
+      },
+      "list": {
+        "minimalRole": "owner",
+        "parameters": {},
+        "results": {
+          "blackList": {
+            "type": "array",
+            "items": {
+              "type": "object",
+              "properties": {
+                "userId": {
+                  "type": "string"
+                },
+                "applicationId": {
+                  "type": "string"
+                }
+              },
+              "additionalProperties": false
+            }
+          }
+        }
+      }
+    },
+    "state": {
+      "size": {
+        "type": "integer",
+        "isRequired": true
+      },
+      "capacity": {
+        "type": "integer",
+        "isRequired": true
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(expected, *trait);
+
+  EXPECT_JSON_EQ((R"({
+    "capacity": 10,
+    "size": 0
+  })"),
+                 *GetState());
+}
+
+TEST_F(AccessApiHandlerTest, Block) {
+  EXPECT_CALL(access_manager_, Block(std::vector<uint8_t>{1, 2, 3},
+                                     std::vector<uint8_t>{3, 4, 5}, _, _))
+      .WillOnce(WithArgs<3>(
+          Invoke([](const DoneCallback& callback) { callback.Run(nullptr); })));
+  EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(1));
+
+  AddCommand(R"({
+    'name' : '_accessControlBlackList.block',
+    'component': 'accessControl',
+    'parameters': {
+      'userId': 'AQID',
+      'applicationId': 'AwQF',
+      'expirationTimeoutSec': 1234
+    }
+  })");
+  EXPECT_JSON_EQ((R"({
+    "capacity": 10,
+    "size": 1
+  })"),
+                 *GetState());
+}
+
+TEST_F(AccessApiHandlerTest, Unblock) {
+  EXPECT_CALL(access_manager_, Unblock(std::vector<uint8_t>{1, 2, 3},
+                                       std::vector<uint8_t>{3, 4, 5}, _))
+      .WillOnce(Return(true));
+  EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4));
+
+  AddCommand(R"({
+    'name' : '_accessControlBlackList.unblock',
+    'component': 'accessControl',
+    'parameters': {
+      'userId': 'AQID',
+      'applicationId': 'AwQF',
+      'expirationTimeoutSec': 1234
+    }
+  })");
+  EXPECT_JSON_EQ((R"({
+    "capacity": 10,
+    "size": 4
+  })"),
+                 *GetState());
+}
+
+TEST_F(AccessApiHandlerTest, List) {
+  std::vector<AccessBlackListManager::Entry> entries{
+      {{11, 12, 13}, {21, 22, 23}, base::Time::FromTimeT(1410000000)},
+      {{31, 32, 33}, {41, 42, 43}, base::Time::FromTimeT(1420000000)},
+  };
+  EXPECT_CALL(access_manager_, GetEntries()).WillOnce(Return(entries));
+  EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4));
+
+  auto expected = R"({
+    "blackList": [ {
+      "applicationId": "FRYX",
+      "userId": "CwwN"
+    }, {
+       "applicationId": "KSor",
+       "userId": "HyAh"
+    } ]
+  })";
+
+  EXPECT_JSON_EQ(expected, AddCommand(R"({
+    'name' : '_accessControlBlackList.list',
+    'component': 'accessControl',
+    'parameters': {
+    }
+  })"));
+}
+}  // namespace weave
diff --git a/src/access_black_list_manager.h b/src/access_black_list_manager.h
new file mode 100644
index 0000000..ed30839
--- /dev/null
+++ b/src/access_black_list_manager.h
@@ -0,0 +1,37 @@
+// 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_H_
+#define LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_
+
+#include <vector>
+
+#include <base/time/time.h>
+
+namespace weave {
+
+class AccessBlackListManager {
+ public:
+  struct Entry {
+    std::vector<uint8_t> user_id;
+    std::vector<uint8_t> app_id;
+    base::Time expiration;
+  };
+  virtual ~AccessBlackListManager() = default;
+
+  virtual void Block(const std::vector<uint8_t>& user_id,
+                     const std::vector<uint8_t>& app_id,
+                     const base::Time& expiration,
+                     const DoneCallback& callback) = 0;
+  virtual bool Unblock(const std::vector<uint8_t>& user_id,
+                       const std::vector<uint8_t>& app_id,
+                       ErrorPtr* error) = 0;
+  virtual std::vector<Entry> GetEntries() const = 0;
+  virtual size_t GetSize() const = 0;
+  virtual size_t GetCapacity() const = 0;
+};
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_
diff --git a/src/base_api_handler.h b/src/base_api_handler.h
index 1dbbac8..6eebfca 100644
--- a/src/base_api_handler.h
+++ b/src/base_api_handler.h
@@ -33,7 +33,7 @@
   void OnConfigChanged(const Settings& settings);
 
   DeviceRegistrationInfo* device_info_;
-  Device* device_;
+  Device* device_{nullptr};
 
   base::WeakPtrFactory<BaseApiHandler> weak_ptr_factory_{this};
   DISALLOW_COPY_AND_ASSIGN(BaseApiHandler);