1// Copyright 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/schema_registry.h"
6
7#include "base/logging.h"
8
9namespace policy {
10
11SchemaRegistry::Observer::~Observer() {}
12
13SchemaRegistry::InternalObserver::~InternalObserver() {}
14
15SchemaRegistry::SchemaRegistry() : schema_map_(new SchemaMap) {
16  for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
17    domains_ready_[i] = false;
18#if !defined(ENABLE_EXTENSIONS)
19  domains_ready_[POLICY_DOMAIN_EXTENSIONS] = true;
20#endif
21}
22
23SchemaRegistry::~SchemaRegistry() {
24  FOR_EACH_OBSERVER(InternalObserver,
25                    internal_observers_,
26                    OnSchemaRegistryShuttingDown(this));
27}
28
29void SchemaRegistry::RegisterComponent(const PolicyNamespace& ns,
30                                       const Schema& schema) {
31  ComponentMap map;
32  map[ns.component_id] = schema;
33  RegisterComponents(ns.domain, map);
34}
35
36void SchemaRegistry::RegisterComponents(PolicyDomain domain,
37                                        const ComponentMap& components) {
38  // Don't issue notifications if nothing is being registered.
39  if (components.empty())
40    return;
41  // Assume that a schema was updated if the namespace was already registered
42  // before.
43  DomainMap map(schema_map_->GetDomains());
44  for (ComponentMap::const_iterator it = components.begin();
45       it != components.end(); ++it) {
46    map[domain][it->first] = it->second;
47  }
48  schema_map_ = new SchemaMap(map);
49  Notify(true);
50}
51
52void SchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
53  DomainMap map(schema_map_->GetDomains());
54  if (map[ns.domain].erase(ns.component_id) != 0) {
55    schema_map_ = new SchemaMap(map);
56    Notify(false);
57  } else {
58    NOTREACHED();
59  }
60}
61
62bool SchemaRegistry::IsReady() const {
63  for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i) {
64    if (!domains_ready_[i])
65      return false;
66  }
67  return true;
68}
69
70void SchemaRegistry::SetReady(PolicyDomain domain) {
71  if (domains_ready_[domain])
72    return;
73  domains_ready_[domain] = true;
74  if (IsReady())
75    FOR_EACH_OBSERVER(Observer, observers_, OnSchemaRegistryReady());
76}
77
78void SchemaRegistry::AddObserver(Observer* observer) {
79  observers_.AddObserver(observer);
80}
81
82void SchemaRegistry::RemoveObserver(Observer* observer) {
83  observers_.RemoveObserver(observer);
84}
85
86void SchemaRegistry::AddInternalObserver(InternalObserver* observer) {
87  internal_observers_.AddObserver(observer);
88}
89
90void SchemaRegistry::RemoveInternalObserver(InternalObserver* observer) {
91  internal_observers_.RemoveObserver(observer);
92}
93
94void SchemaRegistry::Notify(bool has_new_schemas) {
95  FOR_EACH_OBSERVER(
96      Observer, observers_, OnSchemaRegistryUpdated(has_new_schemas));
97}
98
99CombinedSchemaRegistry::CombinedSchemaRegistry()
100    : own_schema_map_(new SchemaMap) {
101  // The combined registry is always ready, since it can always start tracking
102  // another registry that is not ready yet and going from "ready" to "not
103  // ready" is not allowed.
104  for (int i = 0; i < POLICY_DOMAIN_SIZE; ++i)
105    SetReady(static_cast<PolicyDomain>(i));
106}
107
108CombinedSchemaRegistry::~CombinedSchemaRegistry() {}
109
110void CombinedSchemaRegistry::Track(SchemaRegistry* registry) {
111  registries_.insert(registry);
112  registry->AddObserver(this);
113  registry->AddInternalObserver(this);
114  // Recombine the maps only if the |registry| has any components other than
115  // POLICY_DOMAIN_CHROME.
116  if (registry->schema_map()->HasComponents())
117    Combine(true);
118}
119
120void CombinedSchemaRegistry::RegisterComponents(
121    PolicyDomain domain,
122    const ComponentMap& components) {
123  DomainMap map(own_schema_map_->GetDomains());
124  for (ComponentMap::const_iterator it = components.begin();
125       it != components.end(); ++it) {
126    map[domain][it->first] = it->second;
127  }
128  own_schema_map_ = new SchemaMap(map);
129  Combine(true);
130}
131
132void CombinedSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
133  DomainMap map(own_schema_map_->GetDomains());
134  if (map[ns.domain].erase(ns.component_id) != 0) {
135    own_schema_map_ = new SchemaMap(map);
136    Combine(false);
137  } else {
138    NOTREACHED();
139  }
140}
141
142void CombinedSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
143  Combine(has_new_schemas);
144}
145
146void CombinedSchemaRegistry::OnSchemaRegistryReady() {
147  // Ignore.
148}
149
150void CombinedSchemaRegistry::OnSchemaRegistryShuttingDown(
151    SchemaRegistry* registry) {
152  registry->RemoveObserver(this);
153  registry->RemoveInternalObserver(this);
154  if (registries_.erase(registry) != 0) {
155    if (registry->schema_map()->HasComponents())
156      Combine(false);
157  } else {
158    NOTREACHED();
159  }
160}
161
162void CombinedSchemaRegistry::Combine(bool has_new_schemas) {
163  // If two registries publish a Schema for the same component then it's
164  // undefined which version gets in the combined registry.
165  //
166  // The common case is that both registries want policy for the same component,
167  // and the Schemas should be the same; in that case this makes no difference.
168  //
169  // But if the Schemas are different then one of the components is out of date.
170  // In that case the policy loaded will be valid only for one of them, until
171  // the outdated components are updated. This is a known limitation of the
172  // way policies are loaded currently, but isn't a problem worth fixing for
173  // the time being.
174  DomainMap map(own_schema_map_->GetDomains());
175  for (std::set<SchemaRegistry*>::const_iterator reg_it = registries_.begin();
176       reg_it != registries_.end(); ++reg_it) {
177    const DomainMap& reg_domain_map = (*reg_it)->schema_map()->GetDomains();
178    for (DomainMap::const_iterator domain_it = reg_domain_map.begin();
179         domain_it != reg_domain_map.end(); ++domain_it) {
180      const ComponentMap& reg_component_map = domain_it->second;
181      for (ComponentMap::const_iterator comp_it = reg_component_map.begin();
182           comp_it != reg_component_map.end(); ++comp_it) {
183        map[domain_it->first][comp_it->first] = comp_it->second;
184      }
185    }
186  }
187  schema_map_ = new SchemaMap(map);
188  Notify(has_new_schemas);
189}
190
191ForwardingSchemaRegistry::ForwardingSchemaRegistry(SchemaRegistry* wrapped)
192    : wrapped_(wrapped) {
193  schema_map_ = wrapped_->schema_map();
194  wrapped_->AddObserver(this);
195  wrapped_->AddInternalObserver(this);
196}
197
198ForwardingSchemaRegistry::~ForwardingSchemaRegistry() {
199  if (wrapped_) {
200    wrapped_->RemoveObserver(this);
201    wrapped_->RemoveInternalObserver(this);
202  }
203}
204
205void ForwardingSchemaRegistry::RegisterComponents(
206    PolicyDomain domain,
207    const ComponentMap& components) {
208  // POLICY_DOMAIN_CHROME is skipped to avoid spurious updates when a new
209  // Profile is created. If the ForwardingSchemaRegistry is used outside
210  // device-level accounts then this should become configurable.
211  if (wrapped_ && domain != POLICY_DOMAIN_CHROME)
212    wrapped_->RegisterComponents(domain, components);
213  // Ignore otherwise.
214}
215
216void ForwardingSchemaRegistry::UnregisterComponent(const PolicyNamespace& ns) {
217  if (wrapped_)
218    wrapped_->UnregisterComponent(ns);
219  // Ignore otherwise.
220}
221
222void ForwardingSchemaRegistry::OnSchemaRegistryUpdated(bool has_new_schemas) {
223  schema_map_ = wrapped_->schema_map();
224  Notify(has_new_schemas);
225}
226
227void ForwardingSchemaRegistry::OnSchemaRegistryReady() {
228  // Ignore.
229}
230
231void ForwardingSchemaRegistry::OnSchemaRegistryShuttingDown(
232    SchemaRegistry* registry) {
233  DCHECK_EQ(wrapped_, registry);
234  wrapped_->RemoveObserver(this);
235  wrapped_->RemoveInternalObserver(this);
236  wrapped_ = NULL;
237  // Keep serving the same |schema_map_|.
238}
239
240}  // namespace policy
241