OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2017 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 "extensions/browser/api/lock_screen_data/item_storage.h" | |
6 | |
7 #include <set> | |
8 #include <utility> | |
9 | |
10 #include "base/files/file_util.h" | |
11 #include "base/guid.h" | |
12 #include "base/memory/ptr_util.h" | |
13 #include "base/task_scheduler/post_task.h" | |
14 #include "base/task_scheduler/task_traits.h" | |
15 #include "base/values.h" | |
16 #include "components/prefs/pref_registry_simple.h" | |
17 #include "components/prefs/pref_service.h" | |
18 #include "components/prefs/scoped_user_pref_update.h" | |
19 #include "extensions/browser/api/lock_screen_data/data_item.h" | |
20 #include "extensions/browser/api/lock_screen_data/operation_result.h" | |
21 #include "extensions/browser/event_router.h" | |
22 #include "extensions/browser/extension_registry.h" | |
23 #include "extensions/browser/extensions_browser_client.h" | |
24 #include "extensions/common/api/lock_screen_data.h" | |
25 | |
26 namespace extensions { | |
27 | |
28 namespace lock_screen_data { | |
29 | |
30 namespace { | |
31 | |
32 ItemStorage* g_data_item_storage = nullptr; | |
33 | |
34 ItemStorage::ItemFactoryCallback* g_test_item_factory_callback = nullptr; | |
35 | |
36 const char kLockScreenDataPrefKey[] = "lockScreenDataItems"; | |
37 | |
38 void DeleteImpl(const base::FilePath& file_path, bool recursive) { | |
39 base::DeleteFile(file_path, recursive); | |
40 } | |
41 | |
42 void DoNothingOnDelete(OperationResult result) {} | |
43 | |
44 std::unique_ptr<DataItem> CreateDataItem(const std::string& item_id) { | |
45 return g_test_item_factory_callback | |
46 ? g_test_item_factory_callback->Run(item_id) | |
47 : base::MakeUnique<DataItem>(item_id); | |
48 } | |
49 | |
50 } // namespace | |
51 | |
52 // static | |
53 ItemStorage* ItemStorage::Get(content::BrowserContext* context) { | |
54 if (g_data_item_storage && !g_data_item_storage->IsContextAllowed(context)) | |
55 return nullptr; | |
56 return g_data_item_storage; | |
rkc
2017/06/22 18:35:47
Nothing guarantees g_data_item_storage is not null
tbarzic
2017/06/26 22:21:42
Yes it's OK - what would happen is that the API me
| |
57 } | |
58 | |
59 // static | |
60 void ItemStorage::RegisterLocalState(PrefRegistrySimple* registry) { | |
61 registry->RegisterDictionaryPref(kLockScreenDataPrefKey); | |
62 } | |
63 | |
64 ItemStorage::ItemStorage(content::BrowserContext* context, | |
65 PrefService* local_state, | |
66 const std::string& crypto_key, | |
67 const base::FilePath& storage_root) | |
68 : context_(context), | |
69 user_id_( | |
70 ExtensionsBrowserClient::Get()->GetUserIdHashFromContext(context)), | |
71 crypto_key_(crypto_key), | |
72 local_state_(local_state), | |
73 storage_root_(storage_root.Append(user_id_)), | |
74 extension_registry_observer_(this) { | |
75 CHECK(!user_id_.empty()); | |
76 extension_registry_observer_.Add(ExtensionRegistry::Get(context)); | |
77 task_runner_ = base::CreateSequencedTaskRunnerWithTraits( | |
78 {base::MayBlock(), base::TaskPriority::BACKGROUND, | |
79 base::TaskShutdownBehavior::SKIP_ON_SHUTDOWN}); | |
80 | |
81 ReloadDataItems(); | |
82 | |
83 DCHECK(!g_data_item_storage); | |
84 g_data_item_storage = this; | |
85 } | |
86 | |
87 ItemStorage::~ItemStorage() { | |
88 DCHECK_EQ(g_data_item_storage, this); | |
89 g_data_item_storage = nullptr; | |
90 } | |
91 | |
92 // static | |
93 void ItemStorage::SetItemFactoryForTesting(ItemFactoryCallback* callback) { | |
94 g_test_item_factory_callback = callback; | |
95 } | |
96 | |
97 void ItemStorage::SetSessionLocked(bool session_locked) { | |
98 SessionLockedState new_state = session_locked | |
99 ? SessionLockedState::kLocked | |
100 : SessionLockedState::kNotLocked; | |
101 if (new_state == session_locked_state_) | |
102 return; | |
103 | |
104 bool was_locked = session_locked_state_ == SessionLockedState::kLocked; | |
105 session_locked_state_ = new_state; | |
106 | |
107 for (auto& extension_items : data_items_) { | |
108 if (extension_items.second.empty()) | |
109 continue; | |
110 | |
111 // If the session state is unlocked, dispatch Item availability events to | |
112 // apps with available data items. | |
113 if (session_locked_state_ == SessionLockedState::kNotLocked) { | |
114 api::lock_screen_data::DataItemsAvailableEvent event_args; | |
115 event_args.was_locked = was_locked; | |
116 | |
117 std::unique_ptr<Event> event = base::MakeUnique<Event>( | |
118 events::LOCK_SCREEN_DATA_ON_DATA_ITEMS_AVAILABLE, | |
119 api::lock_screen_data::OnDataItemsAvailable::kEventName, | |
120 api::lock_screen_data::OnDataItemsAvailable::Create(event_args)); | |
121 EventRouter::Get(context_)->DispatchEventToExtension( | |
122 extension_items.first /* extension ID */, std::move(event)); | |
123 } | |
124 } | |
125 } | |
126 | |
127 const DataItem* ItemStorage::CreateItem(const std::string& extension_id) { | |
128 if (!ExtensionRegistry::Get(context_)->GetExtensionById( | |
129 extension_id, ExtensionRegistry::ENABLED)) { | |
130 return nullptr; | |
131 } | |
132 | |
133 const std::string item_id = base::GenerateGUID(); | |
134 std::unique_ptr<DataItem> item = CreateDataItem(item_id); | |
135 DataItem* item_ptr = item.get(); | |
136 | |
137 { | |
138 // Create item entry in local state prefs, ensuring that all parent paths | |
139 // are properly set-up (in case this is the first entry added for a | |
140 // user/extension). | |
141 DictionaryPrefUpdate update(local_state_, kLockScreenDataPrefKey); | |
142 if (!update->HasKey(user_id_)) { | |
143 update->SetDictionary(user_id_, | |
144 base::MakeUnique<base::DictionaryValue>()); | |
145 } | |
146 | |
147 base::DictionaryValue* user_dict = nullptr; | |
148 if (!update->GetDictionary(user_id_, &user_dict)) | |
149 return nullptr; | |
150 | |
151 if (!user_dict->HasKey(extension_id)) { | |
152 user_dict->SetDictionary(extension_id, | |
153 base::MakeUnique<base::DictionaryValue>()); | |
154 } | |
155 | |
156 base::DictionaryValue* extension_dict = nullptr; | |
157 if (!user_dict->GetDictionary(extension_id, &extension_dict)) | |
158 return nullptr; | |
159 | |
160 extension_dict->SetDictionary(item_id, item->ToValue()); | |
161 } | |
162 | |
163 data_items_[extension_id].emplace(item_id, std::move(item)); | |
164 return item_ptr; | |
165 } | |
166 | |
167 std::vector<const DataItem*> ItemStorage::GetAllForExtension( | |
168 const std::string& extension_id) { | |
169 std::vector<const DataItem*> items; | |
170 ExtensionDataItemMap::iterator extension_items = | |
171 data_items_.find(extension_id); | |
172 if (extension_items == data_items_.end()) | |
173 return items; | |
174 | |
175 for (const auto& item : extension_items->second) { | |
176 if (!item.second) | |
177 continue; | |
178 items.push_back(item.second.get()); | |
179 } | |
180 | |
181 return items; | |
182 } | |
183 | |
184 OperationResult ItemStorage::SetItemContent( | |
185 const std::string& extension_id, | |
186 const std::string& item_id, | |
187 const std::vector<char>& data, | |
188 const ItemStorage::WriteCallback& callback) { | |
189 DataItem* item = FindItem(extension_id, item_id); | |
190 if (!item) | |
191 return OperationResult::kNotFound; | |
192 | |
193 if (item->backing_file().empty()) { | |
194 item->set_backing_file(storage_root_.Append(extension_id).Append(item_id)); | |
195 | |
196 DictionaryPrefUpdate update(local_state_, kLockScreenDataPrefKey); | |
197 update->SetDictionary(GetDataItemPrefKey(extension_id, item->id()), | |
198 item->ToValue()); | |
199 } | |
200 | |
201 return item->Write(data, crypto_key_, task_runner_, callback); | |
202 } | |
203 | |
204 OperationResult ItemStorage::GetItemContent( | |
205 const std::string& extension_id, | |
206 const std::string& item_id, | |
207 const ItemStorage::ReadCallback& callback) { | |
208 DataItem* item = FindItem(extension_id, item_id); | |
209 if (!item) | |
210 return OperationResult::kNotFound; | |
211 | |
212 if (item->backing_file().empty()) | |
213 return OperationResult::kNoBackingFile; | |
214 | |
215 return item->Read(crypto_key_, task_runner_, callback); | |
216 } | |
217 | |
218 OperationResult ItemStorage::DeleteItem(const std::string& extension_id, | |
219 const std::string& item_id) { | |
220 DataItem* item = FindItem(extension_id, item_id); | |
221 if (!item) | |
222 return OperationResult::kNotFound; | |
223 | |
224 { | |
225 DictionaryPrefUpdate update(local_state_, kLockScreenDataPrefKey); | |
226 update->Remove(GetDataItemPrefKey(extension_id, item_id), nullptr); | |
227 } | |
228 | |
229 if (!item->backing_file().empty()) | |
230 item->Delete(task_runner_, base::Bind(&DoNothingOnDelete)); | |
231 | |
232 data_items_[extension_id].erase(item_id); | |
233 return OperationResult::kSuccess; | |
234 } | |
235 | |
236 void ItemStorage::OnExtensionUninstalled( | |
237 content::BrowserContext* browser_context, | |
238 const Extension* extension, | |
239 UninstallReason reason) { | |
240 ClearDataForExtension(extension->id()); | |
241 } | |
242 | |
243 std::string ItemStorage::GetDataItemPrefKey(const std::string& extension_id, | |
244 const std::string& item_id) { | |
245 return user_id_ + "." + extension_id + "." + item_id; | |
246 } | |
247 | |
248 bool ItemStorage::IsContextAllowed(content::BrowserContext* context) { | |
249 switch (session_locked_state_) { | |
250 case SessionLockedState::kUnknown: | |
251 return false; | |
252 case SessionLockedState::kLocked: | |
253 return ExtensionsBrowserClient::Get()->IsLockScreenContext(context); | |
254 case SessionLockedState::kNotLocked: | |
255 return context_ == context; | |
256 } | |
257 NOTREACHED() << "Unknown session locked state"; | |
258 return false; | |
259 } | |
260 | |
261 void ItemStorage::ReloadDataItems() { | |
262 data_items_.clear(); | |
rkc
2017/06/22 18:35:47
Since this is called exactly once and only from th
tbarzic
2017/06/26 22:21:42
Done.
| |
263 | |
264 const base::DictionaryValue* user_data = nullptr; | |
265 const base::DictionaryValue* items = | |
266 local_state_->GetDictionary(kLockScreenDataPrefKey); | |
267 if (!items || !items->GetDictionary(user_id_, &user_data) || !user_data) | |
268 return; | |
269 | |
270 // Set of extensions pref entries that should be remove from prefs (for | |
271 // example, entries for extensions that are not installed anymore). | |
272 // Note that entries are not removed directly during iteration over the | |
273 // dictionary to avoid invalidating data under the iterator. | |
274 std::set<std::string> extensions_to_remove; | |
275 | |
276 for (base::DictionaryValue::Iterator extension_iter(*user_data); | |
277 !extension_iter.IsAtEnd(); extension_iter.Advance()) { | |
278 if (!ExtensionRegistry::Get(context_)->GetInstalledExtension( | |
279 extension_iter.key())) { | |
280 extensions_to_remove.insert(extension_iter.key()); | |
281 } else { | |
282 if (!LoadDataItemsForExtension(extension_iter.key(), | |
283 extension_iter.value())) { | |
284 extensions_to_remove.insert(extension_iter.key()); | |
285 } | |
286 } | |
287 } | |
288 | |
289 // Remove stale/invalid entries. | |
290 for (const auto& extension_id : extensions_to_remove) | |
291 ClearDataForExtension(extension_id); | |
292 } | |
293 | |
294 bool ItemStorage::LoadDataItemsForExtension(const std::string& extension_id, | |
295 const base::Value& extension_pref) { | |
296 const base::DictionaryValue* items = nullptr; | |
297 if (!extension_pref.GetAsDictionary(&items)) | |
298 return false; | |
299 | |
300 std::set<std::string> items_to_remove; | |
301 base::FilePath expected_file_parent = storage_root_.Append(extension_id); | |
302 for (base::DictionaryValue::Iterator item_iter(*items); !item_iter.IsAtEnd(); | |
303 item_iter.Advance()) { | |
304 std::unique_ptr<DataItem> item = CreateDataItem(item_iter.key()); | |
305 | |
306 // Invalid items (including items pointing to an unexpected location) should | |
307 // be removed from prefs - schedule removal after the loop to avoid | |
308 // invalidating the iterator. | |
309 if (!item->InitFromValue(item_iter.value()) || | |
310 (!item->backing_file().empty() && | |
311 !expected_file_parent.IsParent(item->backing_file()))) { | |
312 items_to_remove.insert(item_iter.key()); | |
313 continue; | |
314 } | |
315 data_items_[extension_id].emplace(item_iter.key(), std::move(item)); | |
316 } | |
317 | |
318 DictionaryPrefUpdate update(local_state_, kLockScreenDataPrefKey); | |
319 for (const auto& item_id : items_to_remove) { | |
320 update->Remove(GetDataItemPrefKey(extension_id, item_id), nullptr); | |
321 } | |
322 return true; | |
323 } | |
324 | |
325 void ItemStorage::ClearDataForExtension(const std::string& extension_id) { | |
326 task_runner_->PostTask( | |
327 FROM_HERE, | |
328 base::Bind(&DeleteImpl, storage_root_.Append(extension_id), true)); | |
329 | |
330 data_items_.erase(extension_id); | |
331 | |
332 DictionaryPrefUpdate update(local_state_, kLockScreenDataPrefKey); | |
333 update->Remove(user_id_ + "." + extension_id, nullptr); | |
334 } | |
335 | |
336 DataItem* ItemStorage::FindItem(const std::string& extension_id, | |
337 const std::string& item_id) { | |
338 ExtensionDataItemMap::iterator extension_items = | |
339 data_items_.find(extension_id); | |
340 if (extension_items == data_items_.end()) | |
341 return nullptr; | |
342 | |
343 DataItemMap::iterator item_it = extension_items->second.find(item_id); | |
344 if (item_it == extension_items->second.end()) | |
345 return nullptr; | |
346 | |
347 if (!item_it->second) | |
348 return nullptr; | |
349 return item_it->second.get(); | |
350 } | |
351 | |
352 } // namespace lock_screen_data | |
353 } // namespace extensions | |
OLD | NEW |