diff --git a/src/atomdb/ProtectedAtomDB.cc b/src/atomdb/ProtectedAtomDB.cc index 402aaf3b..eb863400 100644 --- a/src/atomdb/ProtectedAtomDB.cc +++ b/src/atomdb/ProtectedAtomDB.cc @@ -359,6 +359,7 @@ optional ProtectedAtomDB::authorize_read(const string& handle, optional ProtectedAtomDB::authorize_read(const shared_ptr& atom, const shared_ptr& keychain) { + if (atom == nullptr) return nullopt; auto public_key = this->try_get_public_key(keychain); if (!public_key || !this->can_read(atom, *public_key)) return nullopt; return public_key; @@ -374,17 +375,29 @@ set ProtectedAtomDB::check_handles(shared_ptr keychain, shared_ptr ProtectedAtomDB::filter_handle_set( const optional& public_key, const function()>& query) { - if (!public_key) return make_shared(); + if (!public_key) { + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ << ")] filter_handle_set: skipped, no public key"); + return make_shared(); + } + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ << ")] filter_handle_set: querying backend"); auto original_handle_set = query(); - - auto authorized_handle_set = make_shared(); + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ << ")] filter_handle_set: backend returned " + << (original_handle_set ? original_handle_set->size() : 0) + << " handles"); if (original_handle_set == nullptr) { - return authorized_handle_set; + return make_shared(); + } + + if (this->manifest->is_unrestricted(*public_key)) { + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ + << ")] filter_handle_set: unrestricted, skipping authorization"); + return original_handle_set; } auto it = original_handle_set->get_iterator(); + auto authorized_handle_set = make_shared(); while (true) { char* handle_cstr = it->next(); @@ -399,16 +412,18 @@ shared_ptr ProtectedAtomDB::filter_handle_set( } } - LOG_DEBUG("ProtectedAtomDB::filter_handle_set() - original handle_set: " + - std::to_string(original_handle_set->size())); - LOG_DEBUG("ProtectedAtomDB::filter_handle_set() - authorized handle_set: " + - std::to_string(authorized_handle_set->size())); + LOG_DEBUG("[ProtectedAtomDB(" << this->uid_ + << ")] filter_handle_set: " << original_handle_set->size() << " -> " + << authorized_handle_set->size() << " authorized"); return authorized_handle_set; } set ProtectedAtomDB::filter_handles(const set& original_handles, const string& public_key) { + if (this->manifest->is_unrestricted(public_key)) { + return original_handles; + } set authorized_handles; for (const auto& handle : original_handles) { if (this->can_read(handle, public_key)) { diff --git a/src/atomdb/ProtectedAtomDB.h b/src/atomdb/ProtectedAtomDB.h index 34a4cbc0..ed5d91fb 100644 --- a/src/atomdb/ProtectedAtomDB.h +++ b/src/atomdb/ProtectedAtomDB.h @@ -205,10 +205,17 @@ class ProtectedAtomDB : public AtomDB, public KeySensitiveAtomDB { /** * @brief Returns whether the caller may READ the specified handle. + * + * Unrestricted profiles skip loading the atom and skip Manifest matching; + * existence is still required (atom_exists), so a missing handle is not readable. + * Restricted profiles load the atom here and send it to the Manifest. */ inline bool can_read(const string& handle, const string& public_key) { if (public_key.empty()) return false; - return this->manifest->is_granted(public_key, handle, AuthorizationOperation::READ); + if (this->manifest->is_unrestricted(public_key)) { + return this->backend->atom_exists(handle); + } + return this->can_read(this->backend->get_atom(handle), public_key); } /** diff --git a/src/atomdb/auth/AuthorizationManifest.cc b/src/atomdb/auth/AuthorizationManifest.cc index d5c80242..c59548ca 100644 --- a/src/atomdb/auth/AuthorizationManifest.cc +++ b/src/atomdb/auth/AuthorizationManifest.cc @@ -92,6 +92,13 @@ void AuthorizationManifest::count_matching_atoms(size_t& node_count, profile->count_matching_atoms(node_count, link_count, atom_count, keychain); } +bool AuthorizationManifest::is_unrestricted(const string& public_key) { + lock_guard lock(this->profiles_mutex); + auto it = this->profiles.find(public_key); + if (it == this->profiles.end() || it->second == nullptr) return false; + return it->second->is_unrestricted(); +} + // -------------------------------------------------------------------------------- // Private methods diff --git a/src/atomdb/auth/AuthorizationManifest.h b/src/atomdb/auth/AuthorizationManifest.h index 90bf9cb3..3195e88b 100644 --- a/src/atomdb/auth/AuthorizationManifest.h +++ b/src/atomdb/auth/AuthorizationManifest.h @@ -51,6 +51,14 @@ class AuthorizationManifest { size_t& atom_count, shared_ptr keychain); + /** + * @brief Returns whether public_key's loaded profile grants unrestricted access. + * + * @return true if the profile is cached and has full access; false if it is + * missing, not loaded, or schema-restricted. + */ + bool is_unrestricted(const string& public_key); + private: shared_ptr atomdb; map> profiles; diff --git a/src/tests/cpp/authorization_test.cc b/src/tests/cpp/authorization_test.cc index 48df43ec..fbf8e872 100644 --- a/src/tests/cpp/authorization_test.cc +++ b/src/tests/cpp/authorization_test.cc @@ -199,6 +199,24 @@ TEST(AuthorizationManifestTest, IsGranted) { EXPECT_FALSE(manifest.is_granted("pk", string(32, '0'), AuthorizationOperation::WRITE)); } +TEST(AuthorizationManifestTest, IsUnrestricted) { + auto db = make_shared(); + db->permissions["full"] = make_document("full", true, {}); + db->permissions["restricted"] = make_document("restricted", false, {read_only_inheritance_schema()}); + AuthorizationManifest manifest(db); + + EXPECT_FALSE(manifest.is_unrestricted("full")); + EXPECT_FALSE(manifest.is_unrestricted("restricted")); + EXPECT_FALSE(manifest.is_unrestricted("unknown")); + EXPECT_FALSE(manifest.is_unrestricted("")); + + ASSERT_TRUE(manifest.ensure_profile_loaded("full")); + ASSERT_TRUE(manifest.ensure_profile_loaded("restricted")); + EXPECT_TRUE(manifest.is_unrestricted("full")); + EXPECT_FALSE(manifest.is_unrestricted("restricted")); + EXPECT_FALSE(manifest.is_unrestricted("unknown")); +} + TEST(AuthorizationManifestTest, FullAccessGrantsAllOperations) { string link_handle; auto db = db_with_inheritance_link(&link_handle);