// 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 #include #include #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 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::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& user_id, const std::vector& app_id, base::Time timestamp) const { Entry entry_to_find; const std::vector 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 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