Implement minimalRole for state definitions

State definition may now specify the user's minimal role needed
to see the value of the state. When a user with lower access rights
is requesting the component tree, state properties unavailable to
that user will be removed from the resulting JSON object.

BUG: 24622262

Change-Id: I3b75c60e868d14fe9a9eaec373fcb148bfac1188
Reviewed-on: https://weave-review.googlesource.com/2721
Reviewed-by: Alex Vakulenko <avakulenko@google.com>
diff --git a/src/component_manager.h b/src/component_manager.h
index cea5569..1a3d05f 100644
--- a/src/component_manager.h
+++ b/src/component_manager.h
@@ -154,9 +154,14 @@
       const std::string& command_name) const = 0;
 
   // Checks the minimum required user role for a given command.
-  virtual bool GetMinimalRole(const std::string& command_name,
-                              UserRole* minimal_role,
-                              ErrorPtr* error) const = 0;
+  virtual bool GetCommandMinimalRole(const std::string& command_name,
+                                     UserRole* minimal_role,
+                                     ErrorPtr* error) const = 0;
+
+  // Checks the minimum required user role for a given state property.
+  virtual bool GetStateMinimalRole(const std::string& state_property_name,
+                                   UserRole* minimal_role,
+                                   ErrorPtr* error) const = 0;
 
   // Returns the full JSON dictionary containing trait definitions.
   virtual const base::DictionaryValue& GetTraits() const = 0;
@@ -164,6 +169,11 @@
   // Returns the full JSON dictionary containing component instances.
   virtual const base::DictionaryValue& GetComponents() const = 0;
 
+  // Returns a JSON dictionary containing component instances with state
+  // properties visible to a user of the given |role|.
+  virtual std::unique_ptr<base::DictionaryValue> GetComponentsForUserRole(
+      UserRole role) const = 0;
+
   // Component state manipulation methods.
   virtual bool SetStateProperties(const std::string& component_path,
                                   const base::DictionaryValue& dict,
diff --git a/src/component_manager_impl.cc b/src/component_manager_impl.cc
index 3ea1f46..9712a3c 100644
--- a/src/component_manager_impl.cc
+++ b/src/component_manager_impl.cc
@@ -27,6 +27,60 @@
     {UserRole::kOwner, "owner"},
     {UserRole::kManager, "manager"},
 };
+
+void RemoveInaccessibleState(const ComponentManagerImpl* manager,
+                             base::DictionaryValue* component,
+                             UserRole role) {
+  std::vector<std::string> state_props_to_remove;
+  base::DictionaryValue* state = nullptr;
+  if (component->GetDictionary("state", &state)) {
+    for (base::DictionaryValue::Iterator it_trait(*state);
+          !it_trait.IsAtEnd(); it_trait.Advance()) {
+      const base::DictionaryValue* trait = nullptr;
+      CHECK(it_trait.value().GetAsDictionary(&trait));
+      for (base::DictionaryValue::Iterator it_prop(*trait);
+            !it_prop.IsAtEnd(); it_prop.Advance()) {
+        std::string prop_name = base::StringPrintf("%s.%s",
+                                                    it_trait.key().c_str(),
+                                                    it_prop.key().c_str());
+        UserRole minimal_role;
+        if (manager->GetStateMinimalRole(prop_name, &minimal_role, nullptr) &&
+            minimal_role > role) {
+          state_props_to_remove.push_back(prop_name);
+        }
+      }
+    }
+  }
+  // Now remove any inaccessible properties from the state collection.
+  for (const std::string& path : state_props_to_remove) {
+    // Remove starting from component level in order for "state" to be removed
+    // if no sub-properties remain.
+    CHECK(component->RemovePath(base::StringPrintf("state.%s", path.c_str()),
+                                nullptr));
+  }
+
+  // If this component has any sub-components, filter them too.
+  base::DictionaryValue* sub_components = nullptr;
+  if (component->GetDictionary("components", &sub_components)) {
+    for (base::DictionaryValue::Iterator it_component(*sub_components);
+         !it_component.IsAtEnd(); it_component.Advance()) {
+      base::Value* sub_component = nullptr;
+      CHECK(sub_components->Get(it_component.key(), &sub_component));
+      if (sub_component->GetType() == base::Value::TYPE_LIST) {
+        base::ListValue* component_array = nullptr;
+        CHECK(sub_component->GetAsList(&component_array));
+        for (base::Value* item : *component_array) {
+          CHECK(item->GetAsDictionary(&component));
+          RemoveInaccessibleState(manager, component, role);
+        }
+      } else if (sub_component->GetType() == base::Value::TYPE_DICTIONARY) {
+        CHECK(sub_component->GetAsDictionary(&component));
+        RemoveInaccessibleState(manager, component, role);
+      }
+    }
+  }
+}
+
 }  // anonymous namespace
 
 template <>
@@ -230,7 +284,7 @@
     return nullptr;
 
   UserRole minimal_role;
-  if (!GetMinimalRole(command_instance->GetName(), &minimal_role, error))
+  if (!GetCommandMinimalRole(command_instance->GetName(), &minimal_role, error))
     return nullptr;
 
   if (role < minimal_role) {
@@ -346,9 +400,24 @@
   return definition;
 }
 
-bool ComponentManagerImpl::GetMinimalRole(const std::string& command_name,
-                                          UserRole* minimal_role,
-                                          ErrorPtr* error) const {
+const base::DictionaryValue* ComponentManagerImpl::FindStateDefinition(
+    const std::string& state_property_name) const {
+  const base::DictionaryValue* definition = nullptr;
+  std::vector<std::string> components =
+      Split(state_property_name, ".", true, false);
+  // Make sure the |state_property_name| came in form of trait_name.state_name.
+  if (components.size() != 2)
+    return definition;
+  std::string key = base::StringPrintf("%s.state.%s", components[0].c_str(),
+                                       components[1].c_str());
+  traits_.GetDictionary(key, &definition);
+  return definition;
+}
+
+bool ComponentManagerImpl::GetCommandMinimalRole(
+    const std::string& command_name,
+    UserRole* minimal_role,
+    ErrorPtr* error) const {
   const base::DictionaryValue* command = FindCommandDefinition(command_name);
   if (!command) {
     return Error::AddToPrintf(
@@ -363,12 +432,47 @@
   return true;
 }
 
+bool ComponentManagerImpl::GetStateMinimalRole(
+    const std::string& state_property_name,
+    UserRole* minimal_role,
+    ErrorPtr* error) const {
+  const base::DictionaryValue* state = FindStateDefinition(state_property_name);
+  if (!state) {
+    return Error::AddToPrintf(
+        error, FROM_HERE, errors::commands::kInvalidState,
+        "State definition for '%s' not found", state_property_name.c_str());
+  }
+  std::string value;
+  if (state->GetString(kMinimalRole, &value)) {
+    CHECK(StringToEnum(value, minimal_role));
+  } else {
+    *minimal_role = UserRole::kUser;
+  }
+  return true;
+}
+
 void ComponentManagerImpl::AddStateChangedCallback(
     const base::Closure& callback) {
   on_state_changed_.push_back(callback);
   callback.Run();  // Force to read current state.
 }
 
+std::unique_ptr<base::DictionaryValue>
+ComponentManagerImpl::GetComponentsForUserRole(UserRole role) const {
+  std::unique_ptr<base::DictionaryValue> components{components_.DeepCopy()};
+  // Build a list of all state properties that are inaccessible to the given
+  // user. These properties will be removed from the components collection
+  // returned from this method.
+  for (base::DictionaryValue::Iterator it_component(components_);
+       !it_component.IsAtEnd(); it_component.Advance()) {
+    base::DictionaryValue* component = nullptr;
+    CHECK(components->GetDictionary(it_component.key(), &component));
+    RemoveInaccessibleState(this, component, role);
+  }
+
+  return components;
+}
+
 bool ComponentManagerImpl::SetStateProperties(const std::string& component_path,
                                               const base::DictionaryValue& dict,
                                               ErrorPtr* error) {
diff --git a/src/component_manager_impl.h b/src/component_manager_impl.h
index 5b8201a..5550d99 100644
--- a/src/component_manager_impl.h
+++ b/src/component_manager_impl.h
@@ -115,10 +115,20 @@
   const base::DictionaryValue* FindCommandDefinition(
       const std::string& command_name) const override;
 
+  // Finds a state definition, where |state_property_name| is in the form of
+  // "trait.state".
+  const base::DictionaryValue* FindStateDefinition(
+      const std::string& state_property_name) const;
+
   // Checks the minimum required user role for a given command.
-  bool GetMinimalRole(const std::string& command_name,
-                      UserRole* minimal_role,
-                      ErrorPtr* error) const override;
+  bool GetCommandMinimalRole(const std::string& command_name,
+                             UserRole* minimal_role,
+                             ErrorPtr* error) const override;
+
+  // Checks the minimum required user role for a given state.
+  bool GetStateMinimalRole(const std::string& state_property_name,
+                             UserRole* minimal_role,
+                             ErrorPtr* error) const override;
 
   // Returns the full JSON dictionary containing trait definitions.
   const base::DictionaryValue& GetTraits() const override { return traits_; }
@@ -128,6 +138,11 @@
     return components_;
   }
 
+  // Returns a JSON dictionary containing component instances with state
+  // properties visible to a user of the given |role|.
+  std::unique_ptr<base::DictionaryValue> GetComponentsForUserRole(
+      UserRole role) const override;
+
   // Component state manipulation methods.
   bool SetStateProperties(const std::string& component_path,
                           const base::DictionaryValue& dict,
diff --git a/src/component_manager_unittest.cc b/src/component_manager_unittest.cc
index 291ace8..f0db3ef 100644
--- a/src/component_manager_unittest.cc
+++ b/src/component_manager_unittest.cc
@@ -404,7 +404,7 @@
             manager_.FindTraitDefinition("trait1.command1.parameters"));
 }
 
-TEST_F(ComponentManagerTest, GetMinimalRole) {
+TEST_F(ComponentManagerTest, GetCommandMinimalRole) {
   const char kTraits[] = R"({
     "trait1": {
       "commands": {
@@ -423,19 +423,63 @@
   ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));
 
   UserRole role;
-  ASSERT_TRUE(manager_.GetMinimalRole("trait1.command1", &role, nullptr));
+  ASSERT_TRUE(manager_.GetCommandMinimalRole("trait1.command1", &role,
+                                             nullptr));
   EXPECT_EQ(UserRole::kUser, role);
 
-  ASSERT_TRUE(manager_.GetMinimalRole("trait1.command2", &role, nullptr));
+  ASSERT_TRUE(manager_.GetCommandMinimalRole("trait1.command2", &role,
+                                             nullptr));
   EXPECT_EQ(UserRole::kViewer, role);
 
-  ASSERT_TRUE(manager_.GetMinimalRole("trait2.command1", &role, nullptr));
+  ASSERT_TRUE(manager_.GetCommandMinimalRole("trait2.command1", &role,
+                                             nullptr));
   EXPECT_EQ(UserRole::kManager, role);
 
-  ASSERT_TRUE(manager_.GetMinimalRole("trait2.command2", &role, nullptr));
+  ASSERT_TRUE(manager_.GetCommandMinimalRole("trait2.command2", &role,
+                                             nullptr));
   EXPECT_EQ(UserRole::kOwner, role);
 
-  EXPECT_FALSE(manager_.GetMinimalRole("trait1.command3", &role, nullptr));
+  EXPECT_FALSE(manager_.GetCommandMinimalRole("trait1.command3", &role,
+                                              nullptr));
+}
+
+TEST_F(ComponentManagerTest, GetStateMinimalRole) {
+  const char kTraits[] = R"({
+    "trait1": {
+      "state": {
+        "property1": {"type": "integer"},
+        "property2": {"type": "boolean", "minimalRole": "viewer"},
+        "property3": {"type": "integer", "minimalRole": "user"}
+      }
+    },
+    "trait2": {
+      "state": {
+        "property1": {"type": "string", "minimalRole": "manager"},
+        "property2": {"type": "integer", "minimalRole": "owner"}
+      }
+    }
+  })";
+  auto json = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));
+
+  UserRole role;
+  ASSERT_TRUE(manager_.GetStateMinimalRole("trait1.property1", &role, nullptr));
+  EXPECT_EQ(UserRole::kUser, role);
+
+  ASSERT_TRUE(manager_.GetStateMinimalRole("trait1.property2", &role, nullptr));
+  EXPECT_EQ(UserRole::kViewer, role);
+
+  ASSERT_TRUE(manager_.GetStateMinimalRole("trait1.property3", &role, nullptr));
+  EXPECT_EQ(UserRole::kUser, role);
+
+  ASSERT_TRUE(manager_.GetStateMinimalRole("trait2.property1", &role, nullptr));
+  EXPECT_EQ(UserRole::kManager, role);
+
+  ASSERT_TRUE(manager_.GetStateMinimalRole("trait2.property2", &role, nullptr));
+  EXPECT_EQ(UserRole::kOwner, role);
+
+  ASSERT_FALSE(manager_.GetStateMinimalRole("trait2.property3", &role,
+                                            nullptr));
 }
 
 TEST_F(ComponentManagerTest, AddComponent) {
@@ -1267,4 +1311,216 @@
   test::MockComponentManager mock;
 }
 
+TEST_F(ComponentManagerTest, GetComponentsForUserRole) {
+  const char kTraits[] = R"({
+    "trait1": {
+      "state": {
+        "prop1": { "type": "string", "minimalRole": "viewer" },
+        "prop2": { "type": "string", "minimalRole": "user" }
+      }
+    },
+    "trait2": {
+      "state": {
+        "prop3": { "type": "string", "minimalRole": "manager" },
+        "prop4": { "type": "string", "minimalRole": "owner" }
+      }
+    },
+    "trait3": {
+      "state": {
+        "prop5": { "type": "string" }
+      }
+    }
+  })";
+  auto json = CreateDictionaryValue(kTraits);
+  ASSERT_TRUE(manager_.LoadTraits(*json, nullptr));
+  ASSERT_TRUE(manager_.AddComponent("", "comp1", {"trait1", "trait2"},
+                                    nullptr));
+  ASSERT_TRUE(manager_.AddComponent("", "comp2", {"trait2"}, nullptr));
+  ASSERT_TRUE(manager_.AddComponentArrayItem("comp2", "comp3", {"trait3"},
+                                             nullptr));
+  ASSERT_TRUE(manager_.AddComponent("comp2", "comp4", {"trait3"}, nullptr));
+
+  const char kComp1Properties[] = R"({
+    "trait1": { "prop1": "foo", "prop2": "bar" },
+    "trait2": { "prop3": "baz", "prop4": "quux" }
+  })";
+  ASSERT_TRUE(manager_.SetStatePropertiesFromJson("comp1", kComp1Properties,
+                                                  nullptr));
+
+  const char kComp2Properties[] = R"({
+    "trait2": { "prop3": "foo", "prop4": "bar" }
+  })";
+  ASSERT_TRUE(manager_.SetStatePropertiesFromJson("comp2", kComp2Properties,
+                                                  nullptr));
+
+  const char kComp3Properties[] = R"({
+    "trait3": { "prop5": "foo" }
+  })";
+  ASSERT_TRUE(manager_.SetStatePropertiesFromJson("comp2.comp3[0]",
+                                                  kComp3Properties, nullptr));
+
+  const char kComp4Properties[] = R"({
+    "trait3": { "prop5": "bar" }
+  })";
+  ASSERT_TRUE(manager_.SetStatePropertiesFromJson("comp2.comp4",
+                                                  kComp4Properties, nullptr));
+
+  const char kExpected1[] = R"({
+    "comp1": {
+      "traits": [ "trait1", "trait2" ],
+      "state": {
+        "trait1": {
+          "prop1": "foo",
+          "prop2": "bar"
+        },
+        "trait2": {
+          "prop3": "baz",
+          "prop4": "quux"
+        }
+      }
+    },
+    "comp2": {
+      "traits": [ "trait2" ],
+      "state": {
+        "trait2": {
+          "prop3": "foo",
+          "prop4": "bar"
+        }
+      },
+      "components": {
+        "comp3": [
+          {
+            "traits": [ "trait3" ],
+            "state": {
+              "trait3": {
+                "prop5": "foo"
+              }
+            }
+          }
+        ],
+        "comp4": {
+          "traits": [ "trait3" ],
+          "state": {
+            "trait3": {
+              "prop5": "bar"
+            }
+          }
+        }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected1, manager_.GetComponents());
+
+  EXPECT_JSON_EQ(kExpected1,
+                 *manager_.GetComponentsForUserRole(UserRole::kOwner));
+
+  const char kExpected2[] = R"({
+    "comp1": {
+      "traits": [ "trait1", "trait2" ],
+      "state": {
+        "trait1": {
+          "prop1": "foo",
+          "prop2": "bar"
+        },
+        "trait2": {
+          "prop3": "baz"
+        }
+      }
+    },
+    "comp2": {
+      "traits": [ "trait2" ],
+      "state": {
+        "trait2": {
+          "prop3": "foo"
+        }
+      },
+      "components": {
+        "comp3": [
+          {
+            "traits": [ "trait3" ],
+            "state": {
+              "trait3": {
+                "prop5": "foo"
+              }
+            }
+          }
+        ],
+        "comp4": {
+          "traits": [ "trait3" ],
+          "state": {
+            "trait3": {
+              "prop5": "bar"
+            }
+          }
+        }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected2,
+                 *manager_.GetComponentsForUserRole(UserRole::kManager));
+
+  const char kExpected3[] = R"({
+    "comp1": {
+      "traits": [ "trait1", "trait2" ],
+      "state": {
+        "trait1": {
+          "prop1": "foo",
+          "prop2": "bar"
+        }
+      }
+    },
+    "comp2": {
+      "traits": [ "trait2" ],
+      "components": {
+        "comp3": [
+          {
+            "traits": [ "trait3" ],
+            "state": {
+              "trait3": {
+                "prop5": "foo"
+              }
+            }
+          }
+        ],
+        "comp4": {
+          "traits": [ "trait3" ],
+          "state": {
+            "trait3": {
+              "prop5": "bar"
+            }
+          }
+        }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected3,
+                 *manager_.GetComponentsForUserRole(UserRole::kUser));
+
+  const char kExpected4[] = R"({
+    "comp1": {
+      "traits": [ "trait1", "trait2" ],
+      "state": {
+        "trait1": {
+          "prop1": "foo"
+        }
+      }
+    },
+    "comp2": {
+      "traits": [ "trait2" ],
+      "components": {
+        "comp3": [
+          {
+            "traits": [ "trait3" ]
+          }
+        ],
+        "comp4": {
+          "traits": [ "trait3" ]
+        }
+      }
+    }
+  })";
+  EXPECT_JSON_EQ(kExpected4,
+                 *manager_.GetComponentsForUserRole(UserRole::kViewer));
+}
+
 }  // namespace weave
diff --git a/src/privet/cloud_delegate.cc b/src/privet/cloud_delegate.cc
index f565687..0833780 100644
--- a/src/privet/cloud_delegate.cc
+++ b/src/privet/cloud_delegate.cc
@@ -151,8 +151,12 @@
     return device_->GetSettings().xmpp_endpoint;
   }
 
-  const base::DictionaryValue& GetComponents() const override {
-    return component_manager_->GetComponents();
+  std::unique_ptr<base::DictionaryValue> GetComponentsForUser(
+      const UserInfo& user_info) const override {
+    UserRole role;
+    std::string str_scope = EnumToString(user_info.scope());
+    CHECK(StringToEnum(str_scope, &role));
+    return component_manager_->GetComponentsForUserRole(role);
   }
 
   const base::DictionaryValue* FindComponent(const std::string& path,
diff --git a/src/privet/cloud_delegate.h b/src/privet/cloud_delegate.h
index 43b8904..5ae54cf 100644
--- a/src/privet/cloud_delegate.h
+++ b/src/privet/cloud_delegate.h
@@ -100,8 +100,10 @@
   virtual std::string GetServiceUrl() const = 0;
   virtual std::string GetXmppEndpoint() const = 0;
 
-  // Returns dictionary with component tree.
-  virtual const base::DictionaryValue& GetComponents() const = 0;
+  // Returns dictionary with component tree. The components contain only the
+  // state visible to the given user.
+  virtual std::unique_ptr<base::DictionaryValue> GetComponentsForUser(
+      const UserInfo& user_info) const = 0;
 
   // Finds a component at the given path. Return nullptr in case of an error.
   virtual const base::DictionaryValue* FindComponent(const std::string& path,
diff --git a/src/privet/mock_delegates.h b/src/privet/mock_delegates.h
index f04fb37..3fab9d0 100644
--- a/src/privet/mock_delegates.h
+++ b/src/privet/mock_delegates.h
@@ -188,7 +188,8 @@
   MOCK_CONST_METHOD0(GetOAuthUrl, std::string());
   MOCK_CONST_METHOD0(GetServiceUrl, std::string());
   MOCK_CONST_METHOD0(GetXmppEndpoint, std::string());
-  MOCK_CONST_METHOD0(GetComponents, const base::DictionaryValue&());
+  MOCK_CONST_METHOD1(MockGetComponentsForUser,
+                     const base::DictionaryValue&(const UserInfo&));
   MOCK_CONST_METHOD2(FindComponent,
                      const base::DictionaryValue*(const std::string& path,
                                                   ErrorPtr* error));
@@ -228,13 +229,21 @@
     EXPECT_CALL(*this, GetCloudId()).WillRepeatedly(Return("TestCloudId"));
     test_dict_.Set("test", new base::DictionaryValue);
     EXPECT_CALL(*this, GetTraits()).WillRepeatedly(ReturnRef(test_dict_));
-    EXPECT_CALL(*this, GetComponents()).WillRepeatedly(ReturnRef(test_dict_));
+    EXPECT_CALL(*this, MockGetComponentsForUser(_))
+        .WillRepeatedly(ReturnRef(test_dict_));
     EXPECT_CALL(*this, FindComponent(_, _)).Times(0);
   }
 
   ConnectionState connection_state_{ConnectionState::kOnline};
   SetupState setup_state_{SetupState::kNone};
   base::DictionaryValue test_dict_;
+
+ private:
+  std::unique_ptr<base::DictionaryValue> GetComponentsForUser(
+      const UserInfo& user_info) const override {
+    return std::unique_ptr<base::DictionaryValue>{
+        MockGetComponentsForUser(user_info).DeepCopy()};
+  }
 };
 
 }  // namespace privet
diff --git a/src/privet/privet_handler.cc b/src/privet/privet_handler.cc
index 83d5ef3..813812b 100644
--- a/src/privet/privet_handler.cc
+++ b/src/privet/privet_handler.cc
@@ -906,7 +906,8 @@
     auto parts = Split(path, ".", true, false);
     components->Set(parts.back(), CloneComponent(*component, filter).release());
   } else {
-    components = CloneComponentTree(cloud_->GetComponents(), filter);
+    components = CloneComponentTree(*cloud_->GetComponentsForUser(user_info),
+                                    filter);
   }
   base::DictionaryValue output;
   output.Set(kComponentsKey, components.release());
diff --git a/src/privet/privet_handler_unittest.cc b/src/privet/privet_handler_unittest.cc
index ecf4797..b7fb758 100644
--- a/src/privet/privet_handler_unittest.cc
+++ b/src/privet/privet_handler_unittest.cc
@@ -484,6 +484,40 @@
                HandleRequest("/privet/v3/auth", kInput));
 }
 
+TEST_F(PrivetHandlerTest, ComponentsForUser) {
+  auth_header_ = "Privet 123";
+  const UserInfo kOwner{AuthScope::kOwner, TestUserId{"1"}};
+  const UserInfo kManager{AuthScope::kManager, TestUserId{"2"}};
+  const UserInfo kUser{AuthScope::kUser, TestUserId{"3"}};
+  const UserInfo kViewer{AuthScope::kViewer, TestUserId{"4"}};
+  const base::DictionaryValue components;
+  const std::string expected = R"({"components": {}, "fingerprint": "1"})";
+
+  EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kOwner), Return(true)));
+  EXPECT_CALL(cloud_, MockGetComponentsForUser(kOwner))
+      .WillOnce(ReturnRef(components));
+  EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}"));
+
+  EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kManager), Return(true)));
+  EXPECT_CALL(cloud_, MockGetComponentsForUser(kManager))
+      .WillOnce(ReturnRef(components));
+  EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}"));
+
+  EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kUser), Return(true)));
+  EXPECT_CALL(cloud_, MockGetComponentsForUser(kUser))
+      .WillOnce(ReturnRef(components));
+  EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}"));
+
+  EXPECT_CALL(security_, ParseAccessToken(_, _, _))
+      .WillOnce(DoAll(SetArgPointee<1>(kViewer), Return(true)));
+  EXPECT_CALL(cloud_, MockGetComponentsForUser(kViewer))
+      .WillOnce(ReturnRef(components));
+  EXPECT_JSON_EQ(expected, HandleRequest("/privet/v3/components", "{}"));
+}
+
 class PrivetHandlerTestWithAuth : public PrivetHandlerTest {
  public:
   void SetUp() override {
@@ -780,7 +814,8 @@
   base::DictionaryValue components;
   LoadTestJson(kComponents, &components);
   EXPECT_CALL(cloud_, FindComponent(_, _)).WillRepeatedly(Return(nullptr));
-  EXPECT_CALL(cloud_, GetComponents()).WillRepeatedly(ReturnRef(components));
+  EXPECT_CALL(cloud_, MockGetComponentsForUser(_))
+      .WillRepeatedly(ReturnRef(components));
   const char kExpected1[] = R"({
     "components": {
       "comp1": {
diff --git a/src/privet/privet_types.h b/src/privet/privet_types.h
index 0f51862..44be96f 100644
--- a/src/privet/privet_types.h
+++ b/src/privet/privet_types.h
@@ -62,6 +62,14 @@
   AuthScope scope() const { return scope_; }
   const UserAppId& id() const { return id_; }
 
+  bool operator==(const UserInfo& rhs) const {
+    return scope_ == rhs.scope_ && id_ == rhs.id_;
+  }
+
+  bool operator!=(const UserInfo& rhs) const {
+    return scope_ != rhs.scope_ || id_ != rhs.id_;
+  }
+
  private:
   AuthScope scope_;
   UserAppId id_;
diff --git a/src/test/mock_component_manager.h b/src/test/mock_component_manager.h
index 2c1d695..de6ffee 100644
--- a/src/test/mock_component_manager.h
+++ b/src/test/mock_component_manager.h
@@ -65,12 +65,18 @@
   MOCK_CONST_METHOD1(
       FindCommandDefinition,
       const base::DictionaryValue*(const std::string& command_name));
-  MOCK_CONST_METHOD3(GetMinimalRole,
+  MOCK_CONST_METHOD3(GetCommandMinimalRole,
                      bool(const std::string& command_name,
                           UserRole* minimal_role,
                           ErrorPtr* error));
+  MOCK_CONST_METHOD3(GetStateMinimalRole,
+                     bool(const std::string& state_property_name,
+                          UserRole* minimal_role,
+                          ErrorPtr* error));
   MOCK_CONST_METHOD0(GetTraits, const base::DictionaryValue&());
   MOCK_CONST_METHOD0(GetComponents, const base::DictionaryValue&());
+  MOCK_CONST_METHOD1(MockGetComponentsForUserRole,
+                     base::DictionaryValue*(UserRole));
   MOCK_METHOD3(SetStateProperties,
                bool(const std::string& component_path,
                     const base::DictionaryValue& dict,
@@ -118,6 +124,11 @@
       const base::Callback<void(UpdateID)>& callback) override {
     return Token{MockAddServerStateUpdatedCallback(callback)};
   }
+  std::unique_ptr<base::DictionaryValue> GetComponentsForUserRole(
+      UserRole role) const override {
+    return std::unique_ptr<base::DictionaryValue>{
+        MockGetComponentsForUserRole(role)};
+  }
 };
 
 }  // namespace test