mirror of
https://seed.radicle.dev/rad:z3gqcJUoA1n9HaHKufZs5FCSGazv5.git
synced 2026-10-18 19:10:30 +08:00
radicle/storage: Secure canonical_identity_head
Do not iterate over all user namespaces to find the ID of the identity COB, but load from the founder's namespace specifically. If the namespace of the founder is gone, attempt to recover by walking back from `rad/id`.
This commit is contained in:
@@ -271,11 +271,9 @@ fn public_to_private_to_public_replay() {
|
||||
// Recompute and set the identity head, just like `rad id cache` would do.
|
||||
repo.set_identity_head().unwrap();
|
||||
|
||||
// Even though Alice made the repository private, it still appears
|
||||
// as public!
|
||||
assert_eq!(
|
||||
Identity::load(&repo).unwrap().doc().visibility(),
|
||||
&Visibility::Public
|
||||
&Visibility::private([])
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -907,23 +907,57 @@ impl ReadRepository for Repository {
|
||||
}
|
||||
|
||||
fn canonical_identity_head(&self) -> Result<Oid, RepositoryError> {
|
||||
for remote in self.remote_ids()? {
|
||||
let remote = remote?;
|
||||
// Nb. A remote may not have an identity document if the user has not contributed
|
||||
// any changes to the identity COB.
|
||||
let Ok(root) = self.identity_root_of(&remote) else {
|
||||
continue;
|
||||
};
|
||||
let blob = Doc::blob_at(root, self)?;
|
||||
let blob = match self.backend.find_blob(self.id.deref().into()) {
|
||||
Ok(blob) => blob,
|
||||
Err(err) if err.is_not_found() => return Err(RepositoryError::Doc(DocError::Missing)),
|
||||
Err(err) => return Err(err.into()),
|
||||
};
|
||||
|
||||
// We've got an identity that goes back to the correct root.
|
||||
if *self.id == blob.id() {
|
||||
let identity = Identity::get(&root.into(), self)?;
|
||||
let doc = Doc::from_blob(&blob)?;
|
||||
let delegates = doc.delegates();
|
||||
|
||||
return Ok(identity.head());
|
||||
}
|
||||
if delegates.len() != 1 {
|
||||
log::debug!(target: "storage", "Root identity document (blob '{}') has {} delegates, expected exactly 1.", self.id, delegates.len());
|
||||
return Err(RepositoryError::Doc(DocError::Missing));
|
||||
}
|
||||
Err(DocError::Missing.into())
|
||||
|
||||
let founder = delegates.first();
|
||||
|
||||
let root = match self.reference_oid(founder, &git::refs::storage::IDENTITY_ROOT) {
|
||||
Ok(root) => {
|
||||
log::debug!(target: "storage", "Obtained identity COB root commit '{root}' via reference '{}' in namespace of founder '{founder}' of identity document '{}'.", git::refs::storage::IDENTITY_ROOT.as_str(), self.id);
|
||||
root
|
||||
}
|
||||
Err(err) if err.is_not_found() => {
|
||||
// In case the namespace of the founder does not exist,
|
||||
// or does not have `rad/root`, attempt to find the root
|
||||
// by walking backwards from `rad/id`, which tracks the
|
||||
// head of the identity COB.
|
||||
let root = self.identity_root().map_err(|err| {
|
||||
if err.is_not_found() {
|
||||
RepositoryError::Doc(DocError::Missing)
|
||||
} else {
|
||||
err
|
||||
}
|
||||
})?;
|
||||
|
||||
log::debug!(target: "storage", "Obtained identity COB root commit '{root}' via identity root of the repository.");
|
||||
root
|
||||
}
|
||||
Err(err) => {
|
||||
log::debug!(target: "storage", "Failed to find identity root for founder '{founder}' of identity document '{}': {err}", self.id);
|
||||
return Err(RepositoryError::Doc(DocError::Missing));
|
||||
}
|
||||
};
|
||||
|
||||
let doc_at_root = Doc::load_at(root, self)?;
|
||||
|
||||
if doc_at_root.blob != *self.id {
|
||||
log::debug!(target: "storage", "Root identity document (blob '{}') resolved via '{}' does not match the expected identity document (blob '{}').", doc_at_root.blob, CANONICAL_IDENTITY.as_str(), self.id);
|
||||
return Err(RepositoryError::Doc(DocError::Missing));
|
||||
}
|
||||
|
||||
Ok(Identity::get(&root.into(), self)?.head())
|
||||
}
|
||||
|
||||
fn merge_base(&self, left: &Oid, right: &Oid) -> Result<Oid, crate::git::raw::Error> {
|
||||
|
||||
Reference in New Issue
Block a user