Chromium Code Reviews| 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 |