1 // Copyright (c) 2013 The Chromium Authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file.
4
5 #include "components/policy/core/common/cloud/component_cloud_policy_store.h"
6
7 #include <map>
8 #include <string>
9
10 #include "base/basictypes.h"
11 #include "base/bind.h"
12 #include "base/callback.h"
13 #include "base/files/scoped_temp_dir.h"
14 #include "base/memory/ref_counted.h"
15 #include "base/sha1.h"
16 #include "base/test/test_simple_task_runner.h"
17 #include "components/policy/core/common/cloud/cloud_policy_constants.h"
18 #include "components/policy/core/common/cloud/policy_builder.h"
19 #include "components/policy/core/common/cloud/resource_cache.h"
20 #include "components/policy/core/common/external_data_fetcher.h"
21 #include "policy/proto/chrome_extension_policy.pb.h"
22 #include "policy/proto/device_management_backend.pb.h"
23 #include "testing/gmock/include/gmock/gmock.h"
24 #include "testing/gtest/include/gtest/gtest.h"
25
26 namespace em = enterprise_management;
27
28 using testing::Mock;
29
30 namespace policy {
31
32 namespace {
33
34 const char kTestExtension[] = "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa";
35 const char kTestDownload[] = "http://example.com/getpolicy?id=123";
36 const char kTestPolicy[] =
37 "{"
38 " \"Name\": {"
39 " \"Value\": \"disabled\""
40 " },"
41 " \"Second\": {"
42 " \"Value\": \"maybe\","
43 " \"Level\": \"Recommended\""
44 " }"
45 "}";
46
TestPolicyHash()47 std::string TestPolicyHash() {
48 return base::SHA1HashString(kTestPolicy);
49 }
50
NotEqual(const std::string & expected,const std::string & key)51 bool NotEqual(const std::string& expected, const std::string& key) {
52 return key != expected;
53 }
54
True(const std::string & ignored)55 bool True(const std::string& ignored) {
56 return true;
57 }
58
59 class MockComponentCloudPolicyStoreDelegate
60 : public ComponentCloudPolicyStore::Delegate {
61 public:
~MockComponentCloudPolicyStoreDelegate()62 virtual ~MockComponentCloudPolicyStoreDelegate() {}
63
64 MOCK_METHOD0(OnComponentCloudPolicyStoreUpdated, void());
65 };
66
67 } // namespace
68
69 class ComponentCloudPolicyStoreTest : public testing::Test {
70 protected:
SetUp()71 virtual void SetUp() OVERRIDE {
72 ASSERT_TRUE(temp_dir_.CreateUniqueTempDir());
73 cache_.reset(new ResourceCache(
74 temp_dir_.path(),
75 make_scoped_refptr(new base::TestSimpleTaskRunner)));
76 store_.reset(new ComponentCloudPolicyStore(&store_delegate_, cache_.get()));
77 store_->SetCredentials(ComponentPolicyBuilder::kFakeUsername,
78 ComponentPolicyBuilder::kFakeToken);
79
80 builder_.policy_data().set_policy_type(
81 dm_protocol::kChromeExtensionPolicyType);
82 builder_.policy_data().set_settings_entity_id(kTestExtension);
83 builder_.payload().set_download_url(kTestDownload);
84 builder_.payload().set_secure_hash(TestPolicyHash());
85
86 PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kTestExtension);
87 PolicyMap& policy = expected_bundle_.Get(ns);
88 policy.Set("Name", POLICY_LEVEL_MANDATORY, POLICY_SCOPE_USER,
89 base::Value::CreateStringValue("disabled"), NULL);
90 policy.Set("Second", POLICY_LEVEL_RECOMMENDED, POLICY_SCOPE_USER,
91 base::Value::CreateStringValue("maybe"), NULL);
92 }
93
94 // Returns true if the policy exposed by the |store_| is empty.
IsEmpty()95 bool IsEmpty() {
96 return store_->policy().begin() == store_->policy().end();
97 }
98
CreateResponse()99 scoped_ptr<em::PolicyFetchResponse> CreateResponse() {
100 builder_.Build();
101 return make_scoped_ptr(new em::PolicyFetchResponse(builder_.policy()));
102 }
103
CreateSerializedResponse()104 std::string CreateSerializedResponse() {
105 builder_.Build();
106 return builder_.GetBlob();
107 }
108
109 base::ScopedTempDir temp_dir_;
110 scoped_ptr<ResourceCache> cache_;
111 scoped_ptr<ComponentCloudPolicyStore> store_;
112 MockComponentCloudPolicyStoreDelegate store_delegate_;
113 ComponentPolicyBuilder builder_;
114 PolicyBundle expected_bundle_;
115 };
116
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicy)117 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicy) {
118 em::ExternalPolicyData payload;
119 PolicyNamespace ns;
120 EXPECT_TRUE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
121 EXPECT_EQ(POLICY_DOMAIN_EXTENSIONS, ns.domain);
122 EXPECT_EQ(kTestExtension, ns.component_id);
123 EXPECT_EQ(kTestDownload, payload.download_url());
124 EXPECT_EQ(TestPolicyHash(), payload.secure_hash());
125 }
126
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicyWrongUsername)127 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongUsername) {
128 builder_.policy_data().set_username("anotheruser@example.com");
129 em::ExternalPolicyData payload;
130 PolicyNamespace ns;
131 EXPECT_FALSE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
132 }
133
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicyWrongDMToken)134 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyWrongDMToken) {
135 builder_.policy_data().set_request_token("notmytoken");
136 em::ExternalPolicyData payload;
137 PolicyNamespace ns;
138 EXPECT_FALSE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
139 }
140
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicyBadType)141 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadType) {
142 builder_.policy_data().set_policy_type(dm_protocol::kChromeUserPolicyType);
143 em::ExternalPolicyData payload;
144 PolicyNamespace ns;
145 EXPECT_FALSE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
146 }
147
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicyBadDownloadUrl)148 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadDownloadUrl) {
149 builder_.payload().set_download_url("invalidurl");
150 em::ExternalPolicyData payload;
151 PolicyNamespace ns;
152 EXPECT_FALSE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
153 }
154
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicyEmptyDownloadUrl)155 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyEmptyDownloadUrl) {
156 builder_.payload().clear_download_url();
157 builder_.payload().clear_secure_hash();
158 em::ExternalPolicyData payload;
159 PolicyNamespace ns;
160 // This is valid; it's how "no policy" is signalled to the client.
161 EXPECT_TRUE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
162 }
163
TEST_F(ComponentCloudPolicyStoreTest,ValidatePolicyBadPayload)164 TEST_F(ComponentCloudPolicyStoreTest, ValidatePolicyBadPayload) {
165 builder_.clear_payload();
166 builder_.policy_data().set_policy_value("broken");
167 em::ExternalPolicyData payload;
168 PolicyNamespace ns;
169 EXPECT_FALSE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
170 }
171
TEST_F(ComponentCloudPolicyStoreTest,ValidateNoCredentials)172 TEST_F(ComponentCloudPolicyStoreTest, ValidateNoCredentials) {
173 store_.reset(new ComponentCloudPolicyStore(&store_delegate_, cache_.get()));
174 em::ExternalPolicyData payload;
175 PolicyNamespace ns;
176 EXPECT_FALSE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
177 }
178
TEST_F(ComponentCloudPolicyStoreTest,ValidateWrongCredentials)179 TEST_F(ComponentCloudPolicyStoreTest, ValidateWrongCredentials) {
180 em::ExternalPolicyData payload;
181 PolicyNamespace ns;
182 // Verify that the default response validates with the right credentials.
183 EXPECT_TRUE(store_->ValidatePolicy(CreateResponse(), &ns, &payload));
184 // Now store that response.
185 EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
186 EXPECT_TRUE(store_->Store(
187 ns, CreateSerializedResponse(), TestPolicyHash(), kTestPolicy));
188 Mock::VerifyAndClearExpectations(&store_delegate_);
189 EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
190 // And verify that the response data in the cache.
191 std::map<std::string, std::string> contents;
192 cache_->LoadAllSubkeys("extension-policy", &contents);
193 EXPECT_FALSE(contents.empty());
194
195 // Try loading the cached response data with wrong credentials.
196 ComponentCloudPolicyStore another_store(&store_delegate_, cache_.get());
197 another_store.SetCredentials("wrongdude@example.com", "wrongtoken");
198 another_store.Load();
199 const PolicyBundle empty_bundle;
200 EXPECT_TRUE(another_store.policy().Equals(empty_bundle));
201
202 // The failure to read wiped the cache.
203 cache_->LoadAllSubkeys("extension-policy", &contents);
204 EXPECT_TRUE(contents.empty());
205 }
206
TEST_F(ComponentCloudPolicyStoreTest,StoreAndLoad)207 TEST_F(ComponentCloudPolicyStoreTest, StoreAndLoad) {
208 // Initially empty.
209 EXPECT_TRUE(IsEmpty());
210 store_->Load();
211 EXPECT_TRUE(IsEmpty());
212
213 // Store policy for an unsupported domain.
214 PolicyNamespace ns(POLICY_DOMAIN_CHROME, kTestExtension);
215 builder_.policy_data().set_policy_type(dm_protocol::kChromeUserPolicyType);
216 EXPECT_FALSE(store_->Store(
217 ns, CreateSerializedResponse(), TestPolicyHash(), kTestPolicy));
218
219 // Store policy with the wrong hash.
220 builder_.policy_data().set_policy_type(
221 dm_protocol::kChromeExtensionPolicyType);
222 ns.domain = POLICY_DOMAIN_EXTENSIONS;
223 builder_.payload().set_secure_hash("badash");
224 EXPECT_FALSE(store_->Store(
225 ns, CreateSerializedResponse(), "badash", kTestPolicy));
226
227 // Store policy without a hash.
228 builder_.payload().clear_secure_hash();
229 EXPECT_FALSE(store_->Store(
230 ns, CreateSerializedResponse(), std::string(), kTestPolicy));
231
232 // Store policy with invalid JSON data.
233 static const char kInvalidData[] = "{ not json }";
234 const std::string invalid_data_hash = base::SHA1HashString(kInvalidData);
235 builder_.payload().set_secure_hash(invalid_data_hash);
236 EXPECT_FALSE(store_->Store(
237 ns, CreateSerializedResponse(), invalid_data_hash, kInvalidData));
238
239 // All of those failed.
240 EXPECT_TRUE(IsEmpty());
241 EXPECT_EQ(std::string(), store_->GetCachedHash(ns));
242
243 // Now store a valid policy.
244 builder_.payload().set_secure_hash(TestPolicyHash());
245 EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
246 EXPECT_TRUE(store_->Store(
247 ns, CreateSerializedResponse(), TestPolicyHash(), kTestPolicy));
248 Mock::VerifyAndClearExpectations(&store_delegate_);
249 EXPECT_FALSE(IsEmpty());
250 EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
251 EXPECT_EQ(TestPolicyHash(), store_->GetCachedHash(ns));
252
253 // Loading from the cache validates the policy data again.
254 ComponentCloudPolicyStore another_store(&store_delegate_, cache_.get());
255 another_store.SetCredentials(ComponentPolicyBuilder::kFakeUsername,
256 ComponentPolicyBuilder::kFakeToken);
257 another_store.Load();
258 EXPECT_TRUE(another_store.policy().Equals(expected_bundle_));
259 EXPECT_EQ(TestPolicyHash(), another_store.GetCachedHash(ns));
260 }
261
TEST_F(ComponentCloudPolicyStoreTest,Updates)262 TEST_F(ComponentCloudPolicyStoreTest, Updates) {
263 // Store some policies.
264 PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kTestExtension);
265 EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
266 EXPECT_TRUE(store_->Store(
267 ns, CreateSerializedResponse(), TestPolicyHash(), kTestPolicy));
268 Mock::VerifyAndClearExpectations(&store_delegate_);
269 EXPECT_FALSE(IsEmpty());
270 EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
271
272 // Deleting a non-existant namespace doesn't trigger updates.
273 PolicyNamespace ns_fake(POLICY_DOMAIN_EXTENSIONS, "nosuchid");
274 store_->Delete(ns_fake);
275 Mock::VerifyAndClearExpectations(&store_delegate_);
276
277 // Deleting a namespace that has policies triggers an update.
278 EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
279 store_->Delete(ns);
280 Mock::VerifyAndClearExpectations(&store_delegate_);
281 }
282
TEST_F(ComponentCloudPolicyStoreTest,Purge)283 TEST_F(ComponentCloudPolicyStoreTest, Purge) {
284 // Store a valid policy.
285 EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
286 PolicyNamespace ns(POLICY_DOMAIN_EXTENSIONS, kTestExtension);
287 EXPECT_TRUE(store_->Store(
288 ns, CreateSerializedResponse(), TestPolicyHash(), kTestPolicy));
289 Mock::VerifyAndClearExpectations(&store_delegate_);
290 EXPECT_FALSE(IsEmpty());
291 EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
292
293 // Purge other components.
294 store_->Purge(POLICY_DOMAIN_EXTENSIONS,
295 base::Bind(&NotEqual, kTestExtension));
296
297 // The policy for |ns| is still served.
298 EXPECT_TRUE(store_->policy().Equals(expected_bundle_));
299
300 // Loading the store again will still see |ns|.
301 ComponentCloudPolicyStore another_store(&store_delegate_, cache_.get());
302 const PolicyBundle empty_bundle;
303 EXPECT_TRUE(another_store.policy().Equals(empty_bundle));
304 another_store.SetCredentials(ComponentPolicyBuilder::kFakeUsername,
305 ComponentPolicyBuilder::kFakeToken);
306 another_store.Load();
307 EXPECT_TRUE(another_store.policy().Equals(expected_bundle_));
308
309 // Now purge everything.
310 EXPECT_CALL(store_delegate_, OnComponentCloudPolicyStoreUpdated());
311 store_->Purge(POLICY_DOMAIN_EXTENSIONS, base::Bind(&True));
312 Mock::VerifyAndClearExpectations(&store_delegate_);
313
314 // No policies are served anymore.
315 EXPECT_TRUE(store_->policy().Equals(empty_bundle));
316
317 // And they aren't loaded anymore either.
318 ComponentCloudPolicyStore yet_another_store(&store_delegate_, cache_.get());
319 yet_another_store.SetCredentials(ComponentPolicyBuilder::kFakeUsername,
320 ComponentPolicyBuilder::kFakeToken);
321 yet_another_store.Load();
322 EXPECT_TRUE(yet_another_store.policy().Equals(empty_bundle));
323 }
324
325 } // namespace policy
326