| // 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 <memory> | |
| #include <base/json/json_reader.h> | |
| #include <base/json/json_writer.h> | |
| #include <base/memory/ptr_util.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_) { | |
| std::unique_ptr<base::DictionaryValue> entry = | |
| base::MakeUnique<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::Shrink() { | |
| base::Time oldest[2] = {base::Time::Max(), base::Time::Max()}; | |
| for (auto i = begin(entries_); i != end(entries_);) { | |
| if (i->expiration <= clock_->Now()) | |
| i = entries_.erase(i); | |
| else { | |
| // Non-strict comparison to ensure counting same timestamps as different. | |
| if (i->revocation <= oldest[0]) { | |
| oldest[1] = oldest[0]; | |
| oldest[0] = i->revocation; | |
| } else { | |
| oldest[1] = std::min(oldest[1], i->revocation); | |
| } | |
| ++i; | |
| } | |
| } | |
| CHECK_GT(capacity_, 1u); | |
| if (entries_.size() >= capacity_) { | |
| // List is full so we are going to remove oldest entries from the list. | |
| for (auto i = begin(entries_); i != end(entries_);) { | |
| if (i->revocation <= oldest[1]) | |
| i = entries_.erase(i); | |
| else { | |
| ++i; | |
| } | |
| } | |
| // And replace with a single rule to block everything older. | |
| Entry all_blocking_entry; | |
| all_blocking_entry.expiration = base::Time::Max(); | |
| all_blocking_entry.revocation = oldest[1]; | |
| entries_.insert(all_blocking_entry); | |
| } | |
| } | |
| void AccessRevocationManagerImpl::AddEntryAddedCallback( | |
| const base::Closure& callback) { | |
| on_entry_added_callbacks_.push_back(callback); | |
| } | |
| void AccessRevocationManagerImpl::Block(const Entry& entry, | |
| const DoneCallback& callback) { | |
| 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; | |
| } | |
| // Iterating is OK as Save below is more expensive. | |
| Shrink(); | |
| CHECK_LT(entries_.size(), capacity_); | |
| 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; | |
| const std::vector<uint8_t> no_id; | |
| for (const auto& user : {no_id, user_id}) { | |
| for (const auto& app : {no_id, 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 |