Merge remote-tracking branch 'weave/master' into 'weave/dev'

ec47eb0 examples/provider/wifi_manager: find iface name
43bf6b7 Update gtest
e03c094 Include of gtest_prod.h only building unittests
0dbbf60 AddTo will return AddToTypeProxy for convenience
48a8669 Remove domain from weave::Error
50a147a Enforce printf format literals checking
diff --git a/examples/daemon/common/daemon.h b/examples/daemon/common/daemon.h
index 4cccff3..6dc021d 100644
--- a/examples/daemon/common/daemon.h
+++ b/examples/daemon/common/daemon.h
@@ -69,10 +69,11 @@
   };
 
   Daemon(const Options& opts)
-      : config_store_{new weave::examples::FileConfigStore(
-            opts.disable_security_,
-            opts.model_id_)},
-        task_runner_{new weave::examples::EventTaskRunner},
+      : task_runner_{new weave::examples::EventTaskRunner},
+        config_store_{
+            new weave::examples::FileConfigStore(opts.disable_security_,
+                                                 opts.model_id_,
+                                                 task_runner_.get())},
         http_client_{new weave::examples::CurlHttpClient(task_runner_.get())},
         network_{new weave::examples::EventNetworkImpl(task_runner_.get())},
         bluetooth_{new weave::examples::BluetoothImpl} {
@@ -114,8 +115,8 @@
       LOG(INFO) << "Device registered: " << device->GetSettings().cloud_id;
   }
 
-  std::unique_ptr<weave::examples::FileConfigStore> config_store_;
   std::unique_ptr<weave::examples::EventTaskRunner> task_runner_;
+  std::unique_ptr<weave::examples::FileConfigStore> config_store_;
   std::unique_ptr<weave::examples::CurlHttpClient> http_client_;
   std::unique_ptr<weave::examples::EventNetworkImpl> network_;
   std::unique_ptr<weave::examples::BluetoothImpl> bluetooth_;
diff --git a/examples/provider/file_config_store.cc b/examples/provider/file_config_store.cc
index 6faa242..31efaa7 100644
--- a/examples/provider/file_config_store.cc
+++ b/examples/provider/file_config_store.cc
@@ -12,16 +12,27 @@
 #include <string>
 #include <vector>
 
+#include <base/bind.h>
+
 namespace weave {
 namespace examples {
 
 const char kSettingsDir[] = "/var/lib/weave/";
 
 FileConfigStore::FileConfigStore(bool disable_security,
-                                 const std::string& model_id)
+                                 const std::string& model_id,
+                                 provider::TaskRunner* task_runner)
     : disable_security_{disable_security},
       model_id_{model_id},
-      settings_path_{"/var/lib/weave/weave_settings_" + model_id + ".json"} {}
+      task_runner_{task_runner} {}
+
+std::string FileConfigStore::GetPath(const std::string& name) const {
+  std::string path{kSettingsDir};
+  path += path + "weave_settings_" + model_id_;
+  if (!name.empty())
+    path += "_" + name;
+  return path + ".json";
+}
 
 bool FileConfigStore::LoadDefaults(Settings* settings) {
   char host_name[HOST_NAME_MAX] = {};
@@ -55,17 +66,25 @@
 }
 
 std::string FileConfigStore::LoadSettings() {
-  LOG(INFO) << "Loading settings from " << settings_path_;
-  std::ifstream str(settings_path_);
+  return LoadSettings("");
+}
+
+std::string FileConfigStore::LoadSettings(const std::string& name) {
+  LOG(INFO) << "Loading settings from " << GetPath(name);
+  std::ifstream str(GetPath(name));
   return std::string(std::istreambuf_iterator<char>(str),
                      std::istreambuf_iterator<char>());
 }
 
-void FileConfigStore::SaveSettings(const std::string& settings) {
+void FileConfigStore::SaveSettings(const std::string& name,
+                                   const std::string& settings,
+                                   const DoneCallback& callback) {
   CHECK(mkdir(kSettingsDir, S_IRWXU) == 0 || errno == EEXIST);
-  LOG(INFO) << "Saving settings to " << settings_path_;
-  std::ofstream str(settings_path_);
+  LOG(INFO) << "Saving settings to " << GetPath(name);
+  std::ofstream str(GetPath(name));
   str << settings;
+  if (!callback.is_null())
+    task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr), {});
 }
 
 }  // namespace examples
diff --git a/examples/provider/file_config_store.h b/examples/provider/file_config_store.h
index 578f940..e7398d1 100644
--- a/examples/provider/file_config_store.h
+++ b/examples/provider/file_config_store.h
@@ -10,22 +10,30 @@
 #include <vector>
 
 #include <weave/provider/config_store.h>
+#include <weave/provider/task_runner.h>
 
 namespace weave {
 namespace examples {
 
 class FileConfigStore : public provider::ConfigStore {
  public:
-  FileConfigStore(bool disable_security, const std::string& model_id);
+  FileConfigStore(bool disable_security,
+                  const std::string& model_id,
+                  provider::TaskRunner* task_runner);
 
   bool LoadDefaults(Settings* settings) override;
+  std::string LoadSettings(const std::string& name) override;
+  void SaveSettings(const std::string& name,
+                    const std::string& settings,
+                    const DoneCallback& callback) override;
+
   std::string LoadSettings() override;
-  void SaveSettings(const std::string& settings) override;
 
  private:
+  std::string GetPath(const std::string& name) const;
   const bool disable_security_;
   const std::string model_id_;
-  const std::string settings_path_;
+  provider::TaskRunner* task_runner_{nullptr};
 };
 
 }  // namespace examples
diff --git a/file_lists.mk b/file_lists.mk
index a018178..8dccd9c 100644
--- a/file_lists.mk
+++ b/file_lists.mk
@@ -3,6 +3,8 @@
 # found in the LICENSE file.
 
 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 \
@@ -48,6 +50,8 @@
 	src/test/unittest_utils.cc
 
 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 \
@@ -162,4 +166,3 @@
 	third_party/libuweave/src/macaroon_caveat.c \
 	third_party/libuweave/src/macaroon_context.c \
 	third_party/libuweave/src/macaroon_encoding.c
-
diff --git a/include/weave/provider/config_store.h b/include/weave/provider/config_store.h
index 1b7988f..128eccc 100644
--- a/include/weave/provider/config_store.h
+++ b/include/weave/provider/config_store.h
@@ -13,6 +13,7 @@
 #include <base/callback.h>
 #include <base/time/time.h>
 #include <weave/enum_to_string.h>
+#include <weave/error.h>
 #include <weave/settings.h>
 
 namespace weave {
@@ -36,8 +37,8 @@
 // Implementation of LoadSettings() method should load previously
 // stored settings from the persistent storage (file, flash, etc).
 // For example:
-//   std::string FileConfigStore::LoadSettings() {
-//     std::ifstream str("/var/lib/weave/weave_settings.json");
+//   std::string FileConfigStore::LoadSettings(const std::string& name) {
+//     std::ifstream str("/var/lib/weave/weave_" + name + ".json");
 //     return std::string(std::istreambuf_iterator<char>(str),
 //                        std::istreambuf_iterator<char>());
 //   }
@@ -47,9 +48,14 @@
 // Implementation of SaveSettings(...) method should store data in the
 // persistent storage (file, flash, etc).
 // For example:
-//   void FileConfigStore::SaveSettings(const std::string& settings) {
-//     std::ofstream str(kSettingsPath);
+//   void FileConfigStore::SaveSettings(const std::string& name,
+//                                      const std::string& settings,
+//                                      const DoneCallback& callback) {
+//     std::ofstream str("/var/lib/weave/weave_" + name + ".json");
 //     str << settings;
+//     if (!callback.is_null())
+//       task_runner_->PostDelayedTask(FROM_HERE, base::Bind(callback, nullptr),
+//                                     {});
 //   }
 // It is highly recommended to protected data using encryption with
 // hardware backed key.
@@ -67,12 +73,20 @@
 
   // Returns settings saved by SaveSettings during last run of libweave.
   // Implementation should return data as-is without parsing or modifications.
-  virtual std::string LoadSettings() = 0;
+  // |name| is the name of settings blob. Could be used as filename.
+  virtual std::string LoadSettings(const std::string& name) = 0;
 
   // Saves settings. Implementation should save data as-is without parsing or
   // modifications. Data stored in settings can be sensitive, so it's highly
   // recommended to protect data, e.g. using encryption.
-  virtual void SaveSettings(const std::string& settings) = 0;
+  // |name| is the name of settings blob. Could be used as filename.
+  // Implementation must call or post callback
+  virtual void SaveSettings(const std::string& name,
+                            const std::string& settings,
+                            const DoneCallback& callback) = 0;
+
+  // Deprecated: only for migration of old configs to version with |name|.
+  virtual std::string LoadSettings() = 0;
 
  protected:
   virtual ~ConfigStore() {}
diff --git a/include/weave/provider/test/mock_config_store.h b/include/weave/provider/test/mock_config_store.h
index 3873251..a7eb374 100644
--- a/include/weave/provider/test/mock_config_store.h
+++ b/include/weave/provider/test/mock_config_store.h
@@ -18,10 +18,13 @@
 
 class MockConfigStore : public ConfigStore {
  public:
-  MockConfigStore() {
+  explicit MockConfigStore(bool set_expectations = true) {
     using testing::_;
     using testing::Return;
 
+    if (!set_expectations)
+      return;
+
     EXPECT_CALL(*this, LoadDefaults(_))
         .WillRepeatedly(testing::Invoke([](Settings* settings) {
           settings->firmware_version = "TEST_FIRMWARE";
@@ -39,11 +42,21 @@
           "version": 1,
           "device_id": "TEST_DEVICE_ID"
         })"));
-    EXPECT_CALL(*this, SaveSettings(_)).WillRepeatedly(Return());
+    EXPECT_CALL(*this, LoadSettings(_)).WillRepeatedly(Return(""));
+    EXPECT_CALL(*this, SaveSettings(_, _, _))
+        .WillRepeatedly(testing::WithArgs<1, 2>(testing::Invoke(
+            [](const std::string& json, const DoneCallback& callback) {
+              if (!callback.is_null())
+                callback.Run(nullptr);
+            })));
   }
   MOCK_METHOD1(LoadDefaults, bool(Settings*));
+  MOCK_METHOD1(LoadSettings, std::string(const std::string&));
+  MOCK_METHOD3(SaveSettings,
+               void(const std::string&,
+                    const std::string&,
+                    const DoneCallback&));
   MOCK_METHOD0(LoadSettings, std::string());
-  MOCK_METHOD1(SaveSettings, void(const std::string&));
 };
 
 }  // namespace test
diff --git a/src/access_api_handler.cc b/src/access_api_handler.cc
new file mode 100644
index 0000000..9fa6df2
--- /dev/null
+++ b/src/access_api_handler.cc
@@ -0,0 +1,229 @@
+// 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;
+  }
+
+  manager_->Unblock(user_id, app_id,
+                    base::Bind(&AccessApiHandler::OnCommandDone,
+                               weak_ptr_factory_.GetWeakPtr(), cmd));
+}
+
+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..a142735
--- /dev/null
+++ b/src/access_api_handler_unittest.cc
@@ -0,0 +1,257 @@
+// 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,
+               void(const std::vector<uint8_t>&,
+                    const std::vector<uint8_t>&,
+                    const DoneCallback&));
+  MOCK_CONST_METHOD2(IsBlocked,
+                     bool(const std::vector<uint8_t>&,
+                          const std::vector<uint8_t>&));
+  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);
+
+  expected = R"({
+    "capacity": 10,
+    "size": 0
+  })";
+  EXPECT_JSON_EQ(expected, *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
+    }
+  })");
+
+  auto expected = R"({
+    "capacity": 10,
+    "size": 1
+  })";
+  EXPECT_JSON_EQ(expected, *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(WithArgs<2>(
+          Invoke([](const DoneCallback& callback) { callback.Run(nullptr); })));
+  EXPECT_CALL(access_manager_, GetSize()).WillRepeatedly(Return(4));
+
+  AddCommand(R"({
+    'name' : '_accessControlBlackList.unblock',
+    'component': 'accessControl',
+    'parameters': {
+      'userId': 'AQID',
+      'applicationId': 'AwQF',
+      'expirationTimeoutSec': 1234
+    }
+  })");
+
+  auto expected = R"({
+    "capacity": 10,
+    "size": 4
+  })";
+  EXPECT_JSON_EQ(expected, *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"
+    } ]
+  })";
+
+  const auto& results = AddCommand(R"({
+    'name' : '_accessControlBlackList.list',
+    'component': 'accessControl',
+    'parameters': {
+    }
+  })");
+
+  EXPECT_JSON_EQ(expected, results);
+}
+}  // namespace weave
diff --git a/src/access_black_list_manager.h b/src/access_black_list_manager.h
new file mode 100644
index 0000000..b56226a
--- /dev/null
+++ b/src/access_black_list_manager.h
@@ -0,0 +1,56 @@
+// 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 {
+    // user_id is empty, app_id is empty: block everything.
+    // user_id is not empty, app_id is empty: block if user_id matches.
+    // user_id is empty, app_id is not empty: block if app_id matches.
+    // user_id is not empty, app_id is not empty: block if both match.
+    std::vector<uint8_t> user_id;
+    std::vector<uint8_t> app_id;
+
+    // Time after which to discard the rule.
+    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 void Unblock(const std::vector<uint8_t>& user_id,
+                       const std::vector<uint8_t>& app_id,
+                       const DoneCallback& callback) = 0;
+  virtual bool IsBlocked(const std::vector<uint8_t>& user_id,
+                         const std::vector<uint8_t>& app_id) const = 0;
+  virtual std::vector<Entry> GetEntries() const = 0;
+  virtual size_t GetSize() const = 0;
+  virtual size_t GetCapacity() const = 0;
+};
+
+inline bool operator==(const AccessBlackListManager::Entry& l,
+                       const AccessBlackListManager::Entry& r) {
+  return l.user_id == r.user_id && l.app_id == r.app_id &&
+         l.expiration == r.expiration;
+}
+
+inline bool operator!=(const AccessBlackListManager::Entry& l,
+                       const AccessBlackListManager::Entry& r) {
+  return !(l == r);
+}
+
+}  // namespace weave
+
+#endif  // LIBWEAVE_SRC_ACCESS_BLACK_LIST_H_
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/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);
diff --git a/src/config.cc b/src/config.cc
index 76be205..44d20dd 100644
--- a/src/config.cc
+++ b/src/config.cc
@@ -18,9 +18,12 @@
 #include "src/data_encoding.h"
 #include "src/privet/privet_types.h"
 #include "src/string_utils.h"
+#include "src/bind_lambda.h"
 
 namespace weave {
 
+const char kConfigName[] = "config";
+
 namespace config_keys {
 
 const char kVersion[] = "version";
@@ -139,9 +142,12 @@
 void Config::Transaction::LoadState() {
   if (!config_->config_store_)
     return;
-  std::string json_string = config_->config_store_->LoadSettings();
-  if (json_string.empty())
-    return;
+  std::string json_string = config_->config_store_->LoadSettings(kConfigName);
+  if (json_string.empty()) {
+    json_string = config_->config_store_->LoadSettings();
+    if (json_string.empty())
+      return;
+  }
 
   auto value = base::JSONReader::Read(json_string);
   base::DictionaryValue* dict = nullptr;
@@ -266,7 +272,9 @@
   base::JSONWriter::WriteWithOptions(
       dict, base::JSONWriter::OPTIONS_PRETTY_PRINT, &json_string);
 
-  config_store_->SaveSettings(json_string);
+  config_store_->SaveSettings(
+      kConfigName, json_string,
+      base::Bind([](ErrorPtr error) { CHECK(!error); }));
 }
 
 Config::Transaction::~Transaction() {
diff --git a/src/config_unittest.cc b/src/config_unittest.cc
index 0367516..fbb558a 100644
--- a/src/config_unittest.cc
+++ b/src/config_unittest.cc
@@ -17,18 +17,20 @@
 using testing::_;
 using testing::Invoke;
 using testing::Return;
+using testing::WithArgs;
 
 namespace weave {
 
+const char kConfigName[] = "config";
+
 class ConfigTest : public ::testing::Test {
  protected:
   void SetUp() override {
-    EXPECT_CALL(*this, OnConfigChanged(_))
-        .Times(1);  // Called from AddOnChangedCallback
     Reload();
   }
 
   void Reload() {
+    EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
     config_.reset(new Config{&config_store_});
     config_->AddOnChangedCallback(
         base::Bind(&ConfigTest::OnConfigChanged, base::Unretained(this)));
@@ -86,31 +88,45 @@
 }
 
 TEST_F(ConfigTest, LoadStateV0) {
-  EXPECT_CALL(config_store_, LoadSettings())
+  EXPECT_CALL(config_store_, LoadSettings(kConfigName))
       .WillOnce(Return(R"({
     "device_id": "state_device_id"
   })"));
 
-  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
   Reload();
 
   EXPECT_EQ("state_device_id", GetSettings().cloud_id);
   EXPECT_FALSE(GetSettings().device_id.empty());
   EXPECT_NE(GetSettings().cloud_id, GetSettings().device_id);
 
-  EXPECT_CALL(config_store_, LoadSettings())
+  EXPECT_CALL(config_store_, LoadSettings(kConfigName))
       .WillOnce(Return(R"({
     "device_id": "state_device_id",
     "cloud_id": "state_cloud_id"
   })"));
 
-  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
   Reload();
 
   EXPECT_EQ("state_cloud_id", GetSettings().cloud_id);
   EXPECT_EQ("state_device_id", GetSettings().device_id);
 }
 
+TEST_F(ConfigTest, LoadStateUnnamed) {
+  EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return(""));
+
+  EXPECT_CALL(config_store_, LoadSettings()).Times(1);
+
+  Reload();
+}
+
+TEST_F(ConfigTest, LoadStateNamed) {
+  EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return("{}"));
+
+  EXPECT_CALL(config_store_, LoadSettings()).Times(0);
+
+  Reload();
+}
+
 TEST_F(ConfigTest, LoadState) {
   auto state = R"({
     "version": 1,
@@ -133,9 +149,8 @@
     "secret": "c3RhdGVfc2VjcmV0",
     "service_url": "state_service_url"
   })";
-  EXPECT_CALL(config_store_, LoadSettings()).WillOnce(Return(state));
+  EXPECT_CALL(config_store_, LoadSettings(kConfigName)).WillOnce(Return(state));
 
-  EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
   Reload();
 
   EXPECT_EQ("state_client_id", GetSettings().client_id);
@@ -243,9 +258,10 @@
 
   EXPECT_CALL(*this, OnConfigChanged(_)).Times(1);
 
-  EXPECT_CALL(config_store_, SaveSettings(_))
-      .WillOnce(Invoke([](const std::string& json) {
-        auto expected = R"({
+  EXPECT_CALL(config_store_, SaveSettings(kConfigName, _, _))
+      .WillOnce(WithArgs<1, 2>(
+          Invoke([](const std::string& json, const DoneCallback& callback) {
+            auto expected = R"({
           'version': 1,
           'api_key': 'set_api_key',
           'client_id': 'set_client_id',
@@ -266,8 +282,9 @@
           'secret': 'AQIDBAU=',
           'service_url': 'set_service_url'
         })";
-        EXPECT_JSON_EQ(expected, *test::CreateValue(json));
-      }));
+            EXPECT_JSON_EQ(expected, *test::CreateValue(json));
+            callback.Run(nullptr);
+          })));
 
   change.Commit();
 }
diff --git a/src/device_manager.cc b/src/device_manager.cc
index 04d7a6b..8eed558 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};
diff --git a/src/weave_unittest.cc b/src/weave_unittest.cc
index 5bef931..ebc66cd 100644
--- a/src/weave_unittest.cc
+++ b/src/weave_unittest.cc
@@ -204,10 +204,6 @@
             })));
   }
 
-  void InitConfigStore() {
-    EXPECT_CALL(config_store_, SaveSettings("")).WillRepeatedly(Return());
-  }
-
   void InitNetwork() {
     EXPECT_CALL(network_, AddConnectionChangedCallback(_))
         .WillRepeatedly(Invoke(
@@ -267,7 +263,6 @@
   }
 
   void InitDefaultExpectations() {
-    InitConfigStore();
     InitNetwork();
     EXPECT_CALL(wifi_, StartAccessPoint(MatchesRegex("TEST_NAME.*prv")))
         .WillOnce(Return());
@@ -360,13 +355,11 @@
 }
 
 TEST_F(WeaveTest, StartMinimal) {
-  InitConfigStore();
   device_ = weave::Device::Create(&config_store_, &task_runner_, &http_client_,
                                   &network_, nullptr, nullptr, &wifi_, nullptr);
 }
 
 TEST_F(WeaveTest, StartNoWifi) {
-  InitConfigStore();
   InitNetwork();
   InitHttpServer();
   InitDnsSd();
@@ -450,7 +443,6 @@
   void SetUp() override {
     WeaveTest::SetUp();
 
-    InitConfigStore();
     InitHttpServer();
     InitNetwork();
     InitDnsSd();