blob: c27215159428a1e9b096c0aeaac7cd7e27b28a74 [file] [log] [blame]
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +00001// 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 "chrome/browser/policy/cloud/component_cloud_policy_service.h"
6
7#include "base/bind.h"
8#include "base/bind_helpers.h"
9#include "base/location.h"
10#include "base/logging.h"
Torne (Richard Coles)b2df76e2013-05-13 16:52:09 +010011#include "base/message_loop/message_loop_proxy.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000012#include "base/pickle.h"
13#include "base/sequenced_task_runner.h"
14#include "base/stl_util.h"
15#include "chrome/browser/policy/cloud/component_cloud_policy_store.h"
16#include "chrome/browser/policy/cloud/component_cloud_policy_updater.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000017#include "chrome/browser/policy/cloud/resource_cache.h"
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +010018#include "chrome/browser/policy/proto/cloud/device_management_backend.pb.h"
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +000019#include "content/public/browser/browser_thread.h"
20#include "net/url_request/url_request_context_getter.h"
21
22namespace em = enterprise_management;
23
24namespace policy {
25
26const char ComponentCloudPolicyService::kComponentNamespaceCache[] =
27 "component-namespace-cache";
28
29ComponentCloudPolicyService::Delegate::~Delegate() {}
30
31// Owns the objects that live on the background thread, and posts back to UI
32// to the service whenever the policy changes.
33class ComponentCloudPolicyService::Backend
34 : public ComponentCloudPolicyStore::Delegate {
35 public:
36 Backend(base::WeakPtr<ComponentCloudPolicyService> service,
37 scoped_refptr<base::SequencedTaskRunner> task_runner,
38 scoped_ptr<ResourceCache> cache);
39 virtual ~Backend();
40
41 // This is invoked right after the constructor but on the backend background
42 // thread. Used to create the store on the right thread.
43 void Init();
44
45 // Reads the initial list of components and the initial policy.
46 void FinalizeInit();
47
48 // Creates the backend updater.
49 void Connect(scoped_refptr<net::URLRequestContextGetter> request_context);
50
51 // Stops updating remote data. Cached policies are still served.
52 void Disconnect();
53
54 // Loads the initial policies from the store. |username| and |dm_token| are
55 // used to validate the cached policies.
56 void SetCredentials(const std::string& username, const std::string& dm_token);
57
58 // Passes a policy protobuf to the backend, to start its validation and
59 // eventual download of the policy data on the background thread.
60 // This is ignored if the backend isn't connected.
61 void UpdateExternalPolicy(scoped_ptr<em::PolicyFetchResponse> response);
62
63 // ComponentCloudPolicyStore::Delegate implementation:
64 virtual void OnComponentCloudPolicyStoreUpdated() OVERRIDE;
65
66 // Passes the current list of components in |domain|, so that the disk cache
67 // can purge components that aren't being tracked anymore.
68 void SetCurrentComponents(PolicyDomain domain, const StringSet* components);
69
70 private:
71 scoped_ptr<ComponentMap> ReadCachedComponents();
72
73 base::WeakPtr<ComponentCloudPolicyService> service_;
74 scoped_refptr<base::SequencedTaskRunner> task_runner_;
75 scoped_ptr<ResourceCache> cache_;
76 scoped_ptr<ComponentCloudPolicyStore> store_;
77 scoped_ptr<ComponentCloudPolicyUpdater> updater_;
78
79 DISALLOW_COPY_AND_ASSIGN(Backend);
80};
81
82ComponentCloudPolicyService::Backend::Backend(
83 base::WeakPtr<ComponentCloudPolicyService> service,
84 scoped_refptr<base::SequencedTaskRunner> task_runner,
85 scoped_ptr<ResourceCache> cache)
86 : service_(service),
87 task_runner_(task_runner),
88 cache_(cache.Pass()) {}
89
90ComponentCloudPolicyService::Backend::~Backend() {}
91
92void ComponentCloudPolicyService::Backend::Init() {
93 DCHECK(!store_);
94 store_.reset(new ComponentCloudPolicyStore(this, cache_.get()));
95}
96
97void ComponentCloudPolicyService::Backend::FinalizeInit() {
98 // Read the components that were cached in the last SetCurrentComponents()
99 // calls for each domain.
100 scoped_ptr<ComponentMap> components = ReadCachedComponents();
101
102 // Read the initial policy.
103 store_->Load();
104 scoped_ptr<PolicyBundle> policy(new PolicyBundle);
105 policy->CopyFrom(store_->policy());
106
107 content::BrowserThread::PostTask(
108 content::BrowserThread::UI, FROM_HERE,
109 base::Bind(&ComponentCloudPolicyService::OnBackendInitialized,
110 service_,
111 base::Passed(&components),
112 base::Passed(&policy)));
113}
114
115void ComponentCloudPolicyService::Backend::SetCredentials(
116 const std::string& username,
117 const std::string& dm_token) {
118 store_->SetCredentials(username, dm_token);
119}
120
121void ComponentCloudPolicyService::Backend::Connect(
122 scoped_refptr<net::URLRequestContextGetter> request_context) {
123 updater_.reset(new ComponentCloudPolicyUpdater(
124 task_runner_, request_context, store_.get()));
125}
126
127void ComponentCloudPolicyService::Backend::Disconnect() {
128 updater_.reset();
129}
130
131void ComponentCloudPolicyService::Backend::UpdateExternalPolicy(
132 scoped_ptr<em::PolicyFetchResponse> response) {
133 if (updater_)
134 updater_->UpdateExternalPolicy(response.Pass());
135}
136
137void ComponentCloudPolicyService::Backend::
138 OnComponentCloudPolicyStoreUpdated() {
139 scoped_ptr<PolicyBundle> bundle(new PolicyBundle);
140 bundle->CopyFrom(store_->policy());
141 content::BrowserThread::PostTask(
142 content::BrowserThread::UI, FROM_HERE,
143 base::Bind(&ComponentCloudPolicyService::OnPolicyUpdated,
144 service_,
145 base::Passed(&bundle)));
146}
147
148void ComponentCloudPolicyService::Backend::SetCurrentComponents(
149 PolicyDomain domain,
150 const StringSet* components) {
151 // Store the current list of components in the cache.
152 std::string policy_type;
153 if (ComponentCloudPolicyStore::GetPolicyType(domain, &policy_type)) {
154 Pickle pickle;
155 for (StringSet::const_iterator it = components->begin();
156 it != components->end(); ++it) {
157 pickle.WriteString(*it);
158 }
159 std::string data(reinterpret_cast<const char*>(pickle.data()),
160 pickle.size());
161 cache_->Store(kComponentNamespaceCache, policy_type, data);
162 }
163
164 // Purge any components that have been removed.
165 if (store_)
166 store_->Purge(domain, *components);
167}
168
169scoped_ptr<ComponentCloudPolicyService::ComponentMap>
170 ComponentCloudPolicyService::Backend::ReadCachedComponents() {
171 scoped_ptr<ComponentMap> components(new ComponentMap);
172 std::map<std::string, std::string> contents;
173 cache_->LoadAllSubkeys(kComponentNamespaceCache, &contents);
174 for (std::map<std::string, std::string>::iterator it = contents.begin();
175 it != contents.end(); ++it) {
176 PolicyDomain domain;
177 if (ComponentCloudPolicyStore::GetPolicyDomain(it->first, &domain)) {
178 StringSet& set = (*components)[domain];
179 const Pickle pickle(it->second.data(), it->second.size());
180 PickleIterator pickit(pickle);
181 std::string id;
182 while (pickit.ReadString(&id))
183 set.insert(id);
184 } else {
185 cache_->Delete(kComponentNamespaceCache, it->first);
186 }
187 }
188 return components.Pass();
189}
190
191ComponentCloudPolicyService::ComponentCloudPolicyService(
192 Delegate* delegate,
193 CloudPolicyStore* store,
194 scoped_ptr<ResourceCache> cache)
195 : delegate_(delegate),
196 backend_(NULL),
197 client_(NULL),
198 store_(store),
199 is_initialized_(false),
Torne (Richard Coles)c2e0dbd2013-05-09 18:35:53 +0100200 weak_ptr_factory_(this) {
Torne (Richard Coles)2a99a7e2013-03-28 15:31:22 +0000201 store_->AddObserver(this);
202
203 // TODO(joaodasilva): this can't currently live on the blocking pool because
204 // creating URLFetchers requires a MessageLoop.
205 backend_task_runner_ = content::BrowserThread::GetMessageLoopProxyForThread(
206 content::BrowserThread::FILE);
207 backend_ = new Backend(weak_ptr_factory_.GetWeakPtr(),
208 backend_task_runner_,
209 cache.Pass());
210 backend_task_runner_->PostTask(
211 FROM_HERE, base::Bind(&Backend::Init, base::Unretained(backend_)));
212
213 if (store_->is_initialized())
214 InitializeBackend();
215}
216
217ComponentCloudPolicyService::~ComponentCloudPolicyService() {
218 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
219 store_->RemoveObserver(this);
220 if (client_)
221 client_->RemoveObserver(this);
222 backend_task_runner_->DeleteSoon(FROM_HERE, backend_);
223 backend_ = NULL;
224}
225
226// static
227bool ComponentCloudPolicyService::SupportsDomain(PolicyDomain domain) {
228 return ComponentCloudPolicyStore::SupportsDomain(domain);
229}
230
231void ComponentCloudPolicyService::Connect(
232 CloudPolicyClient* client,
233 scoped_refptr<net::URLRequestContextGetter> request_context) {
234 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
235 DCHECK(!client_);
236 client_ = client;
237 client_->AddObserver(this);
238 // Create the updater in the backend.
239 backend_task_runner_->PostTask(FROM_HERE,
240 base::Bind(&Backend::Connect,
241 base::Unretained(backend_),
242 request_context));
243 if (is_initialized())
244 InitializeClient();
245}
246
247void ComponentCloudPolicyService::Disconnect() {
248 if (client_) {
249 // Unregister all the current components.
250 for (ComponentMap::iterator it = registered_components_.begin();
251 it != registered_components_.end(); ++it) {
252 RemoveNamespacesToFetch(it->first, it->second);
253 }
254
255 client_->RemoveObserver(this);
256 client_ = NULL;
257
258 backend_task_runner_->PostTask(
259 FROM_HERE,
260 base::Bind(&Backend::Disconnect, base::Unretained(backend_)));
261 }
262}
263
264void ComponentCloudPolicyService::RegisterPolicyDomain(
265 PolicyDomain domain,
266 const std::set<std::string>& current_ids) {
267 DCHECK(content::BrowserThread::CurrentlyOn(content::BrowserThread::UI));
268 DCHECK(SupportsDomain(domain));
269
270 // Send the new set to the backend, to purge the cache.
271 backend_task_runner_->PostTask(
272 FROM_HERE,
273 base::Bind(&Backend::SetCurrentComponents,
274 base::Unretained(backend_),
275 domain,
276 base::Owned(new StringSet(current_ids))));
277
278 // Register the current list of components for |domain| at the |client_|.
279 StringSet& registered_ids = registered_components_[domain];
280 if (client_ && is_initialized()) {
281 if (UpdateClientNamespaces(domain, registered_ids, current_ids))
282 delegate_->OnComponentCloudPolicyRefreshNeeded();
283 }
284 registered_ids = current_ids;
285}
286
287void ComponentCloudPolicyService::OnPolicyFetched(CloudPolicyClient* client) {
288 DCHECK_EQ(client_, client);
289 // Pass each PolicyFetchResponse whose policy type is registered to the
290 // Backend.
291 const CloudPolicyClient::ResponseMap& responses = client_->responses();
292 for (CloudPolicyClient::ResponseMap::const_iterator it = responses.begin();
293 it != responses.end(); ++it) {
294 const PolicyNamespaceKey& key(it->first);
295 PolicyDomain domain;
296 if (ComponentCloudPolicyStore::GetPolicyDomain(key.first, &domain) &&
297 ContainsKey(registered_components_[domain], key.second)) {
298 scoped_ptr<em::PolicyFetchResponse> response(
299 new em::PolicyFetchResponse(*it->second));
300 backend_task_runner_->PostTask(FROM_HERE,
301 base::Bind(&Backend::UpdateExternalPolicy,
302 base::Unretained(backend_),
303 base::Passed(&response)));
304 }
305 }
306}
307
308void ComponentCloudPolicyService::OnRegistrationStateChanged(
309 CloudPolicyClient* client) {
310 // Ignored.
311}
312
313void ComponentCloudPolicyService::OnClientError(CloudPolicyClient* client) {
314 // Ignored.
315}
316
317void ComponentCloudPolicyService::OnStoreLoaded(CloudPolicyStore* store) {
318 DCHECK_EQ(store_, store);
319 if (store_->is_initialized()) {
320 if (is_initialized()) {
321 // The backend is already initialized; update the credentials, in case
322 // a new dmtoken or server key became available.
323 SetCredentialsAndReloadClient();
324 } else {
325 // The |store_| just became ready; initialize the backend now.
326 InitializeBackend();
327 }
328 }
329}
330
331void ComponentCloudPolicyService::OnStoreError(CloudPolicyStore* store) {
332 OnStoreLoaded(store);
333}
334
335void ComponentCloudPolicyService::InitializeBackend() {
336 DCHECK(!is_initialized());
337 DCHECK(store_->is_initialized());
338
339 // Set the credentials for the initial policy load, if available.
340 SetCredentialsAndReloadClient();
341
342 backend_task_runner_->PostTask(
343 FROM_HERE,
344 base::Bind(&Backend::FinalizeInit, base::Unretained(backend_)));
345}
346
347void ComponentCloudPolicyService::OnBackendInitialized(
348 scoped_ptr<ComponentMap> cached_components,
349 scoped_ptr<PolicyBundle> initial_policy) {
350 // InitializeBackend() may be called multiple times if the |store_| fires
351 // events while the backend is loading.
352 if (is_initialized())
353 return;
354
355 // RegisterPolicyDomain() may have been called while the backend was
356 // initializing; only update |registered_components_| from |cached_components|
357 // for domains that haven't registered yet.
358 for (ComponentMap::iterator it = cached_components->begin();
359 it != cached_components->end(); ++it) {
360 // Lookup without inserting an empty set.
361 if (registered_components_.find(it->first) != registered_components_.end())
362 continue; // Ignore the cached list if a more recent one was registered.
363 registered_components_[it->first].swap(it->second);
364 }
365
366 // A client may have already connected while the backend was initializing.
367 if (client_)
368 InitializeClient();
369
370 // Set the initial policy, and send the initial update callback.
371 is_initialized_ = true;
372 OnPolicyUpdated(initial_policy.Pass());
373}
374
375void ComponentCloudPolicyService::InitializeClient() {
376 // Register all the current components.
377 bool added = false;
378 for (ComponentMap::iterator it = registered_components_.begin();
379 it != registered_components_.end(); ++it) {
380 added |= !it->second.empty();
381 AddNamespacesToFetch(it->first, it->second);
382 }
383 // The client may already have PolicyFetchResponses for registered components;
384 // load them now.
385 OnPolicyFetched(client_);
386 if (added && is_initialized())
387 delegate_->OnComponentCloudPolicyRefreshNeeded();
388}
389
390void ComponentCloudPolicyService::OnPolicyUpdated(
391 scoped_ptr<PolicyBundle> policy) {
392 policy_.Swap(policy.get());
393 // Don't propagate updates until the initial store Load() has been done.
394 if (is_initialized())
395 delegate_->OnComponentCloudPolicyUpdated();
396}
397
398void ComponentCloudPolicyService::SetCredentialsAndReloadClient() {
399 const em::PolicyData* policy = store_->policy();
400 if (!policy || !policy->has_username() || !policy->has_request_token())
401 return;
402 backend_task_runner_->PostTask(FROM_HERE,
403 base::Bind(&Backend::SetCredentials,
404 base::Unretained(backend_),
405 policy->username(),
406 policy->request_token()));
407 // If this was the initial register, or if the signing key changed, then the
408 // previous OnPolicyFetched() call had its PolicyFetchResponses rejected
409 // because the credentials weren't updated yet. Reload all the responses in
410 // the client now to handle those cases; if those responses have already been
411 // validated then they will be ignored.
412 if (client_)
413 OnPolicyFetched(client_);
414}
415
416bool ComponentCloudPolicyService::UpdateClientNamespaces(
417 PolicyDomain domain,
418 const StringSet& old_set,
419 const StringSet& new_set) {
420 StringSet added = base::STLSetDifference<StringSet>(new_set, old_set);
421 StringSet removed = base::STLSetDifference<StringSet>(old_set, new_set);
422 AddNamespacesToFetch(domain, added);
423 RemoveNamespacesToFetch(domain, removed);
424 return !added.empty();
425}
426
427void ComponentCloudPolicyService::AddNamespacesToFetch(PolicyDomain domain,
428 const StringSet& set) {
429 std::string policy_type;
430 if (ComponentCloudPolicyStore::GetPolicyType(domain, &policy_type)) {
431 for (StringSet::const_iterator it = set.begin(); it != set.end(); ++it)
432 client_->AddNamespaceToFetch(PolicyNamespaceKey(policy_type, *it));
433 }
434}
435
436void ComponentCloudPolicyService::RemoveNamespacesToFetch(
437 PolicyDomain domain,
438 const StringSet& set) {
439 std::string policy_type;
440 if (ComponentCloudPolicyStore::GetPolicyType(domain, &policy_type)) {
441 for (StringSet::const_iterator it = set.begin(); it != set.end(); ++it)
442 client_->RemoveNamespaceToFetch(PolicyNamespaceKey(policy_type, *it));
443 }
444}
445
446} // namespace policy