|  | // Copyright 2016 The Weave Authors. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "src/access_revocation_manager_impl.h" | 
|  |  | 
|  | #include <base/json/json_reader.h> | 
|  | #include <base/json/json_writer.h> | 
|  | #include <base/values.h> | 
|  |  | 
|  | #include "src/commands/schema_constants.h" | 
|  | #include "src/data_encoding.h" | 
|  | #include "src/utils.h" | 
|  |  | 
|  | namespace weave { | 
|  |  | 
|  | namespace { | 
|  | const char kConfigFileName[] = "black_list"; | 
|  |  | 
|  | const char kUser[] = "user"; | 
|  | const char kApp[] = "app"; | 
|  | const char kExpiration[] = "expiration"; | 
|  | const char kRevocation[] = "revocation"; | 
|  | } | 
|  |  | 
|  | AccessRevocationManagerImpl::AccessRevocationManagerImpl( | 
|  | provider::ConfigStore* store, | 
|  | size_t capacity, | 
|  | base::Clock* clock) | 
|  | : capacity_{capacity}, clock_{clock}, store_{store} { | 
|  | Load(); | 
|  | } | 
|  |  | 
|  | void AccessRevocationManagerImpl::Load() { | 
|  | if (!store_) | 
|  | return; | 
|  | if (auto list = base::ListValue::From( | 
|  | base::JSONReader::Read(store_->LoadSettings(kConfigFileName)))) { | 
|  | for (const auto& value : *list) { | 
|  | const base::DictionaryValue* entry{nullptr}; | 
|  | std::string user; | 
|  | std::string app; | 
|  | Entry e; | 
|  | int revocation = 0; | 
|  | int expiration = 0; | 
|  | if (value->GetAsDictionary(&entry) && entry->GetString(kUser, &user) && | 
|  | Base64Decode(user, &e.user_id) && entry->GetString(kApp, &app) && | 
|  | Base64Decode(app, &e.app_id) && | 
|  | entry->GetInteger(kRevocation, &revocation) && | 
|  | entry->GetInteger(kExpiration, &expiration)) { | 
|  | e.revocation = FromJ2000Time(revocation); | 
|  | e.expiration = FromJ2000Time(expiration); | 
|  | if (e.expiration > clock_->Now()) | 
|  | entries_.insert(e); | 
|  | } | 
|  | } | 
|  | if (entries_.size() < list->GetSize()) { | 
|  | // Save some storage space by saving without expired entries. | 
|  | Save({}); | 
|  | } | 
|  | } | 
|  | } | 
|  |  | 
|  | void AccessRevocationManagerImpl::Save(const DoneCallback& callback) { | 
|  | if (!store_) { | 
|  | if (!callback.is_null()) | 
|  | callback.Run(nullptr); | 
|  | return; | 
|  | } | 
|  |  | 
|  | base::ListValue list; | 
|  | for (const auto& e : entries_) { | 
|  | scoped_ptr<base::DictionaryValue> entry{new base::DictionaryValue}; | 
|  | entry->SetString(kUser, Base64Encode(e.user_id)); | 
|  | entry->SetString(kApp, Base64Encode(e.app_id)); | 
|  | entry->SetInteger(kRevocation, ToJ2000Time(e.revocation)); | 
|  | entry->SetInteger(kExpiration, ToJ2000Time(e.expiration)); | 
|  | list.Append(std::move(entry)); | 
|  | } | 
|  |  | 
|  | std::string json; | 
|  | base::JSONWriter::Write(list, &json); | 
|  | store_->SaveSettings(kConfigFileName, json, callback); | 
|  | } | 
|  |  | 
|  | void AccessRevocationManagerImpl::RemoveExpired() { | 
|  | for (auto i = begin(entries_); i != end(entries_);) { | 
|  | if (i->expiration <= clock_->Now()) | 
|  | i = entries_.erase(i); | 
|  | else | 
|  | ++i; | 
|  | } | 
|  | } | 
|  |  | 
|  | void AccessRevocationManagerImpl::AddEntryAddedCallback( | 
|  | const base::Closure& callback) { | 
|  | on_entry_added_callbacks_.push_back(callback); | 
|  | } | 
|  |  | 
|  | void AccessRevocationManagerImpl::Block(const Entry& entry, | 
|  | const DoneCallback& callback) { | 
|  | // Iterating is OK as Save below is more expensive. | 
|  | RemoveExpired(); | 
|  | if (entry.expiration <= clock_->Now()) { | 
|  | if (!callback.is_null()) { | 
|  | ErrorPtr error; | 
|  | Error::AddTo(&error, FROM_HERE, "aleady_expired", | 
|  | "Entry already expired"); | 
|  | callback.Run(std::move(error)); | 
|  | } | 
|  | return; | 
|  | } | 
|  | if (entries_.size() >= capacity_) { | 
|  | if (!callback.is_null()) { | 
|  | ErrorPtr error; | 
|  | Error::AddTo(&error, FROM_HERE, "blacklist_is_full", | 
|  | "Unable to store more entries"); | 
|  | callback.Run(std::move(error)); | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | auto existing = entries_.find(entry); | 
|  | if (existing != entries_.end()) { | 
|  | Entry new_entry = entry; | 
|  | new_entry.expiration = std::max(entry.expiration, existing->expiration); | 
|  | new_entry.revocation = std::max(entry.revocation, existing->revocation); | 
|  | entries_.erase(existing); | 
|  | entries_.insert(new_entry); | 
|  | } else { | 
|  | entries_.insert(entry); | 
|  | } | 
|  |  | 
|  | for (const auto& cb : on_entry_added_callbacks_) | 
|  | cb.Run(); | 
|  |  | 
|  | Save(callback); | 
|  | } | 
|  |  | 
|  | bool AccessRevocationManagerImpl::IsBlocked(const std::vector<uint8_t>& user_id, | 
|  | const std::vector<uint8_t>& app_id, | 
|  | base::Time timestamp) const { | 
|  | Entry entry_to_find; | 
|  | for (const auto& user : {{}, user_id}) { | 
|  | for (const auto& app : {{}, app_id}) { | 
|  | entry_to_find.user_id = user; | 
|  | entry_to_find.app_id = app; | 
|  | auto match = entries_.find(entry_to_find); | 
|  | if (match != end(entries_) && match->expiration > clock_->Now() && | 
|  | match->revocation >= timestamp) { | 
|  | return true; | 
|  | } | 
|  | } | 
|  | } | 
|  | return false; | 
|  | } | 
|  |  | 
|  | std::vector<AccessRevocationManager::Entry> | 
|  | AccessRevocationManagerImpl::GetEntries() const { | 
|  | return {begin(entries_), end(entries_)}; | 
|  | } | 
|  |  | 
|  | size_t AccessRevocationManagerImpl::GetSize() const { | 
|  | return entries_.size(); | 
|  | } | 
|  |  | 
|  | size_t AccessRevocationManagerImpl::GetCapacity() const { | 
|  | return capacity_; | 
|  | } | 
|  |  | 
|  | }  // namespace weave |