| // Copyright 2018 The Chromium OS 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 "authpolicy/auth_data_cache.h" |
| |
| #include <utility> |
| |
| #include <base/files/file_path.h> |
| #include <base/files/file_util.h> |
| #include "base/time/clock.h" |
| #include "base/time/default_clock.h" |
| |
| #include "authpolicy/log_colors.h" |
| |
| namespace authpolicy { |
| namespace { |
| |
| constexpr char kLogHeader[] = "Auth Data Cache: "; |
| |
| #define LOG_START(severity) \ |
| LOG_IF(severity, flags_->log_caches()) << kColorCaches << kLogHeader |
| #define PLOG_START(severity) \ |
| PLOG_IF(severity, flags_->log_caches()) << kColorCaches << kLogHeader |
| #define LOG_END kColorReset |
| |
| // Size limit when loading the cached data file (256 kb). |
| constexpr size_t kCacheSizeLimit = 256 * 1024; |
| |
| } // namespace |
| |
| AuthDataCache::AuthDataCache(const protos::DebugFlags* flags) |
| : flags_(flags), clock_(std::make_unique<base::DefaultClock>()) {} |
| |
| AuthDataCache::~AuthDataCache() = default; |
| |
| bool AuthDataCache::Load(const base::FilePath& path) { |
| data_.Clear(); |
| if (!enabled_) |
| return true; |
| |
| if (!base::PathExists(path)) { |
| LOG_START(ERROR) << "File '" << path.value() << "' does not exist" |
| << LOG_END; |
| return false; |
| } |
| |
| std::string data_blob; |
| if (!base::ReadFileToStringWithMaxSize(path, &data_blob, kCacheSizeLimit)) { |
| PLOG_START(ERROR) << "Failed to read '" << path.value() << "'" << LOG_END; |
| return false; |
| } |
| |
| if (!data_.ParseFromString(data_blob)) { |
| LOG_START(ERROR) << "Failed to parse data from string" << LOG_END; |
| return false; |
| } |
| |
| LOG_START(INFO) << "Read '" << path.value() << "'" << LOG_END; |
| return true; |
| } |
| |
| bool AuthDataCache::Save(const base::FilePath& path) { |
| if (!enabled_) |
| return true; |
| |
| std::string data_blob; |
| if (!data_.SerializeToString(&data_blob)) { |
| LOG_START(ERROR) << "Failed to serialize data to string" << LOG_END; |
| return false; |
| } |
| |
| const int data_size = static_cast<int>(data_blob.size()); |
| if (base::WriteFile(path, data_blob.data(), data_size) != data_size) { |
| LOG_START(ERROR) << "Failed to write '" << path.value() << "'" << LOG_END; |
| return false; |
| } |
| |
| // Lock access to authpolicyd rw. |
| int mode = |
| base::FILE_PERMISSION_READ_BY_USER | base::FILE_PERMISSION_WRITE_BY_USER; |
| if (!base::SetPosixFilePermissions(path, mode)) { |
| LOG_START(ERROR) << "Failed to set permissions on '" << path.value() << "'" |
| << LOG_END; |
| return false; |
| } |
| |
| LOG_START(INFO) << "Wrote '" << path.value() << "'" << LOG_END; |
| return true; |
| } |
| |
| void AuthDataCache::Clear() { |
| data_.Clear(); |
| } |
| |
| base::Optional<std::string> AuthDataCache::GetWorkgroup( |
| const std::string& realm) const { |
| const protos::CachedRealmData* realm_data = GetRealmDataForRead(realm); |
| if (!realm_data || !realm_data->has_workgroup()) { |
| LOG_START(INFO) << "No workgroup cached" << LOG_END; |
| return base::nullopt; |
| } |
| LOG_START(INFO) << "Using cached workgroup" << LOG_END; |
| return realm_data->workgroup(); |
| } |
| |
| base::Optional<std::string> AuthDataCache::GetKdcIp( |
| const std::string& realm) const { |
| const protos::CachedRealmData* realm_data = GetRealmDataForRead(realm); |
| if (!realm_data || !realm_data->has_kdc_ip()) { |
| LOG_START(INFO) << "No KDC IP cached" << LOG_END; |
| return base::nullopt; |
| } |
| LOG_START(INFO) << "Using cached KDC IP" << LOG_END; |
| return realm_data->kdc_ip(); |
| } |
| |
| base::Optional<std::string> AuthDataCache::GetDcName( |
| const std::string& realm) const { |
| const protos::CachedRealmData* realm_data = GetRealmDataForRead(realm); |
| if (!realm_data || !realm_data->has_dc_name()) { |
| LOG_START(INFO) << "No DC name cached" << LOG_END; |
| return base::nullopt; |
| } |
| LOG_START(INFO) << "Using cached DC name" << LOG_END; |
| return realm_data->dc_name(); |
| } |
| |
| base::Optional<bool> AuthDataCache::GetIsAffiliated( |
| const std::string& realm) const { |
| const protos::CachedRealmData* realm_data = GetRealmDataForRead(realm); |
| if (!realm_data || !realm_data->has_is_affiliated()) { |
| LOG_START(INFO) << "No affiliation flag cached" << LOG_END; |
| return base::nullopt; |
| } |
| LOG_START(INFO) << "Using cached affiliation flag" << LOG_END; |
| return realm_data->is_affiliated(); |
| } |
| |
| void AuthDataCache::SetWorkgroup(const std::string& realm, |
| const std::string& workgroup) { |
| protos::CachedRealmData* realm_data = GetRealmDataForWrite(realm); |
| if (realm_data) { |
| LOG_START(INFO) << "Setting workgroup" << LOG_END; |
| realm_data->set_workgroup(workgroup); |
| } |
| } |
| |
| void AuthDataCache::SetKdcIp(const std::string& realm, |
| const std::string& kdc_ip) { |
| protos::CachedRealmData* realm_data = GetRealmDataForWrite(realm); |
| if (realm_data) { |
| LOG_START(INFO) << "Setting KDC IP" << LOG_END; |
| realm_data->set_kdc_ip(kdc_ip); |
| } |
| } |
| |
| void AuthDataCache::SetDcName(const std::string& realm, |
| const std::string& dc_name) { |
| protos::CachedRealmData* realm_data = GetRealmDataForWrite(realm); |
| if (realm_data) { |
| LOG_START(INFO) << "Setting DC name" << LOG_END; |
| realm_data->set_dc_name(dc_name); |
| } |
| } |
| |
| void AuthDataCache::SetIsAffiliated(const std::string& realm, |
| bool is_affiliated) { |
| protos::CachedRealmData* realm_data = GetRealmDataForWrite(realm); |
| if (realm_data) { |
| realm_data->set_is_affiliated(is_affiliated); |
| LOG_START(INFO) << "Setting affiliation flag" << LOG_END; |
| } |
| } |
| |
| void AuthDataCache::RemoveEntriesOlderThan(base::TimeDelta max_age) { |
| base::Time now = clock_->Now(); |
| auto realm_data_map = data_.mutable_realm_data(); |
| for (auto it = realm_data_map->begin(); it != realm_data_map->end(); |
| /* empty */) { |
| // Note: If the clock goes backwards for some reason, clear cache as well |
| // just in case the clock was reset. |
| protos::CachedRealmData& realm_data = it->second; |
| base::TimeDelta age = |
| now - base::Time::FromInternalValue(realm_data.cache_time()); |
| if (age < base::TimeDelta() || age >= max_age) { |
| LOG_START(INFO) << "Removing entry from cache (age=" << age << ")" |
| << LOG_END; |
| it = realm_data_map->erase(it); |
| } else { |
| ++it; |
| } |
| } |
| } |
| |
| void AuthDataCache::SetClockForTesting(std::unique_ptr<base::Clock> clock) { |
| clock_ = std::move(clock); |
| } |
| |
| const protos::CachedRealmData* AuthDataCache::GetRealmDataForRead( |
| const std::string& realm) const { |
| if (!enabled_) |
| return nullptr; |
| auto it = data_.realm_data().find(realm); |
| if (it == data_.realm_data().end()) |
| return nullptr; |
| return &it->second; |
| } |
| |
| protos::CachedRealmData* AuthDataCache::GetRealmDataForWrite( |
| const std::string& realm) { |
| if (!enabled_) |
| return nullptr; |
| protos::CachedRealmData* realm_data = &(*data_.mutable_realm_data())[realm]; |
| |
| // Set cache time only on creation, but not on updates. |
| if (!realm_data->has_cache_time()) |
| realm_data->set_cache_time(clock_->Now().ToInternalValue()); |
| |
| return realm_data; |
| } |
| |
| } // namespace authpolicy |