| // 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 |