Radish alpha
h
Radicle Heartwood Protocol & Stack
Radicle
Git (anonymous pull)
Log in to clone via SSH
radicle-cli: more helpful error for non-delegate updates
✗ CI failure Adrian Duke committed 3 months ago
commit b937a93892db3d80833b3cdde3bc37935151e274
parent 60959f7e83e80b3a98d06cddee9ebd4b2cd39183
1 failed (1 total) View logs
4 files changed +113 -2
modified CHANGELOG.md
@@ -15,6 +15,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
  `tags`, `notes`, `rad`, and `cobs`. The restriction is lifted, and the only
  references filtered out are `refs/tmp/heads` – used by `radicle-remote-helper`
  to create temporary patches.
+
- The `rad id` command will provide a better error message when a non-delegate
+
  attempts to modify the identity document.

## Fixed Bugs

added crates/radicle-cli/examples/rad-id-unauthorized-delegate.md
@@ -0,0 +1,8 @@
+
Alice has created a new repository `rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji` with only herself as the sole delegate. After Bob has cloned it, let's ensure he can't add himself as a delegate too:
+

+
``` ~bob (fail)
+
$ rad id update --repo rad:z42hL2jL4XNk6K8oHQaSWfMgCL7ji --title "Add myself!" --delegate did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk --no-confirm
+
✗ Error: did:key:z6Mkt67GdsW7715MEfRuP4pSZxJRJh6kj6Y48WRqVv4N1tRk is not a delegate, and only delegates are allowed to create a revision
+
✗ Hint: bob (you) is attempting to modify the identity document but is not a delegate!
+
```
+

modified crates/radicle-cli/src/commands/id.rs
@@ -19,6 +19,7 @@ use crate::git::unified_diff::Encode as _;
use crate::git::Rev;
use crate::terminal as term;
use crate::terminal::args::Error;
+
use crate::terminal::format::Author;
use crate::terminal::patch::Message;

pub use args::Args;
@@ -193,7 +194,14 @@ pub fn run(args: Args, ctx: impl term::Context) -> anyhow::Result<()> {
                return Ok(());
            }
            let signer = term::signer(&profile)?;
-
            let revision = update(title, description, proposal, &mut identity, &signer)?;
+
            let revision = update(
+
                title,
+
                description,
+
                proposal,
+
                &mut identity,
+
                &signer,
+
                &profile,
+
            )?;

            if revision.is_accepted() && revision.parent == Some(current.id) {
                // Update the canonical head to point to the latest accepted revision.
@@ -420,13 +428,16 @@ fn update<R, G>(
    doc: Doc,
    current: &mut IdentityMut<R>,
    signer: &Device<G>,
+
    profile: &Profile,
) -> anyhow::Result<Revision>
where
    R: WriteRepository + cob::Store<Namespace = NodeId>,
    G: crypto::signature::Signer<crypto::Signature>,
{
    if let Some((title, description)) = edit_title_description(title, description)? {
-
        let id = current.update(title, description, &doc, signer)?;
+
        let id = current
+
            .update(title, description, &doc, signer)
+
            .map_err(|e| on_identity_err(e, profile))?;
        let revision = current
            .revision(&id)
            .ok_or(anyhow!("update failed: revision {id} is missing"))?;
@@ -437,6 +448,46 @@ where
    }
}

+
fn on_identity_err(e: identity::Error, profile: &Profile) -> anyhow::Error {
+
    let e = anyhow::Error::from(e);
+

+
    e.chain()
+
        .find_map(|c| c.downcast_ref::<identity::ApplyError>())
+
        .map(|e| on_apply_err(e, profile))
+
        .unwrap_or(e)
+
}
+

+
fn on_apply_err(e: &identity::ApplyError, profile: &Profile) -> anyhow::Error {
+
    match e {
+
        e @ identity::ApplyError::NonDelegateUnauthorized { author, .. } => {
+
            let nid = NodeId::from(*author);
+
            let labels = Author::new(&nid, profile, false).labels();
+

+
            Error::with_hint(
+
                anyhow!(e.to_string()),
+
                format!(
+
                    "{} {} is attempting to modify the identity document but is not a delegate!",
+
                    labels.0, labels.1
+
                ),
+
            )
+
            .into()
+
        }
+
        e @ radicle::cob::identity::ApplyError::Missing(_)
+
        | e @ radicle::cob::identity::ApplyError::Init(_)
+
        | e @ radicle::cob::identity::ApplyError::InvalidSignature(..)
+
        | e @ radicle::cob::identity::ApplyError::NotAuthorized
+
        | e @ radicle::cob::identity::ApplyError::MissingParent
+
        | e @ radicle::cob::identity::ApplyError::DuplicateVerdict
+
        | e @ radicle::cob::identity::ApplyError::UnexpectedState
+
        | e @ radicle::cob::identity::ApplyError::Redacted
+
        | e @ radicle::cob::identity::ApplyError::DocUnchanged
+
        | e @ radicle::cob::identity::ApplyError::Git(_)
+
        | e @ radicle::cob::identity::ApplyError::Doc(_) => {
+
            anyhow!(e.to_string())
+
        }
+
    }
+
}
+

fn print_diff(
    previous: Option<&RevisionId>,
    current: &RevisionId,
modified crates/radicle-cli/tests/commands.rs
@@ -603,6 +603,56 @@ fn rad_id_multi_delegate() {
}

#[test]
+
fn rad_id_unauthorized_delegate() {
+
    let mut environment = Environment::new();
+
    let alice = environment.node("alice");
+
    let bob = environment.node("bob");
+
    let acme = RepoId::from_str("z42hL2jL4XNk6K8oHQaSWfMgCL7ji").unwrap();
+

+
    environment.repository(&alice);
+

+
    test(
+
        "examples/rad-init.md",
+
        environment.work(&alice),
+
        Some(&alice.home),
+
        [],
+
    )
+
    .unwrap();
+

+
    let mut alice = alice.spawn();
+
    let mut bob = bob.spawn();
+

+
    // Alice sets up the seed
+
    alice.handle.seed(acme, Scope::Followed).unwrap();
+

+
    bob.connect(&alice).converge([&alice]);
+
    bob.rad(
+
        "clone",
+
        &[acme.to_string().as_str()],
+
        environment.work(&bob),
+
    )
+
    .unwrap();
+

+
    formula(
+
        &environment.tempdir(),
+
        "examples/rad-id-unauthorized-delegate.md",
+
    )
+
    .unwrap()
+
    .home(
+
        "alice",
+
        environment.work(&alice),
+
        [("RAD_HOME", alice.home.path().display())],
+
    )
+
    .home(
+
        "bob",
+
        environment.work(&bob),
+
        [("RAD_HOME", bob.home.path().display())],
+
    )
+
    .run()
+
    .unwrap();
+
}
+

+
#[test]
#[ignore = "slow"]
fn rad_id_collaboration() {
    let mut environment = Environment::new();