Skip to content

Commit

Permalink
fix: constrain external ID attestation author in recipes (#117)
Browse files Browse the repository at this point in the history
  • Loading branch information
8e8b2c authored Sep 10, 2024
1 parent b22b434 commit e405c75
Show file tree
Hide file tree
Showing 7 changed files with 82 additions and 7 deletions.
5 changes: 4 additions & 1 deletion crates/holoom_types/src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,10 @@ pub enum RecipeInstruction {
GetDocsListedByVar {
var_name: String,
},
GetLatestCallerExternalId,
GetLatestCallerExternalId {
#[ts(type = "AgentPubKey")]
trusted_author: AgentPubKey,
},
GetCallerAgentPublicKey,
}

Expand Down
4 changes: 2 additions & 2 deletions crates/username_registry_coordinator/src/recipe_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -117,11 +117,11 @@ pub fn execute_recipe(payload: ExecuteRecipePayload) -> ExternResult<Record> {
RecipeInstructionExecution::GetDocsListedByVar { doc_ahs };
(val, instruction_execution)
}
RecipeInstruction::GetLatestCallerExternalId => {
RecipeInstruction::GetLatestCallerExternalId { trusted_author } => {
let mut attestation_records = get_external_id_attestations_for_agent(
GetExternalIdAttestationsForAgentPayload {
agent_pubkey: agent_info()?.agent_initial_pubkey,
trusted_authorities: recipe.trusted_authors.clone(),
trusted_authorities: vec![trusted_author],
},
)?;
let attestation_record =
Expand Down
2 changes: 1 addition & 1 deletion crates/username_registry_validation/src/recipe.rs
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ pub fn validate_create_recipe(
let var_dependencies = match inst {
RecipeInstruction::Constant { .. }
| RecipeInstruction::GetCallerAgentPublicKey
| RecipeInstruction::GetLatestCallerExternalId => Vec::new(),
| RecipeInstruction::GetLatestCallerExternalId { .. } => Vec::new(),
RecipeInstruction::GetDocsListedByVar { var_name } => vec![var_name],
RecipeInstruction::GetLatestDocWithIdentifier { var_name } => vec![var_name],
RecipeInstruction::Jq {
Expand Down
7 changes: 6 additions & 1 deletion crates/username_registry_validation/src/recipe_execution.rs
Original file line number Diff line number Diff line change
Expand Up @@ -128,9 +128,14 @@ pub fn validate_create_recipe_execution(
}
(
RecipeInstructionExecution::GetLatestCallerExternalId { attestation_ah },
RecipeInstruction::GetLatestCallerExternalId,
RecipeInstruction::GetLatestCallerExternalId { trusted_author },
) => {
let attestation_record = must_get_valid_record(attestation_ah)?;
if attestation_record.action().author() != &trusted_author {
return Ok(ValidateCallbackResult::Invalid(
"ExternalIdAttestation is by untrusted author".into(),
));
}
let attestation: ExternalIdAttestation =
deserialize_record_entry(attestation_record)?;
Val::obj(IndexMap::from([
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ test("Can execute basic recipe", async () => {
},
],
["foos", { type: "GetDocsListedByVar", var_name: "foo_name_list" }],
["caller_external_id", { type: "GetLatestCallerExternalId" }],
[
"caller_external_id",
{
type: "GetLatestCallerExternalId",
trusted_author: authority.agentPubKey,
},
],
[
"$return",
{
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { expect, test } from "vitest";
import { runScenario } from "@holochain/tryorama";

import { setupPlayer } from "../utils/setup-happ.js";
import { encodeHashToBase64, fakeAgentPubKey } from "@holochain/client";

test("Cannot use untrusted external id attestor", async () => {
await runScenario(async (scenario) => {
const [alice, aliceCoordinators] = await setupPlayer(scenario);

// Recipe that simply returns an ExternalIdAttestation via
// `GetLatestCallerExternalId`
const externalIdRecipeRecord =
await aliceCoordinators.usernameRegistry.createRecipe({
trusted_authors: [await fakeAgentPubKey(0)],
arguments: [],
instructions: [
[
"$return",
{
type: "GetLatestCallerExternalId",
trusted_author: await fakeAgentPubKey(0),
},
],
],
});

// An ExternalIdAttestation that wasn't created by the trusted author
const externalIdAttestationRecord =
await aliceCoordinators.usernameRegistry.createExternalIdAttestation({
internal_pubkey: alice.agentPubKey,
external_id: "whatever",
display_name: "whatever",
request_id: "1234",
});

// Cannot specify untrusted attestation in execution
await expect(
aliceCoordinators.usernameRegistry.createRecipeExecution({
recipe_ah: externalIdRecipeRecord.signed_action.hashed.hash,
arguments: [],
instruction_executions: [
{
GetLatestCallerExternalId: {
attestation_ah:
externalIdAttestationRecord.signed_action.hashed.hash,
},
},
],
output: JSON.stringify({
agent_pubkey: encodeHashToBase64(alice.agentPubKey),
external_id: "whatever",
display_name: "whatever",
}),
})
).rejects.toSatisfy((err: Error) =>
err.message.includes("ExternalIdAttestation is by untrusted author")
);
});
});
3 changes: 2 additions & 1 deletion packages/types/src/types/RecipeInstruction.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { AgentPubKey } from "@holochain/client";
// This file was generated by [ts-rs](https://github.com/Aleph-Alpha/ts-rs). Do not edit this file manually.
import type { JqInstructionArgumentNames } from "./JqInstructionArgumentNames";

Expand All @@ -6,5 +7,5 @@ export type RecipeInstruction =
| { type: "GetLatestDocWithIdentifier"; var_name: string }
| { type: "Jq"; input_var_names: JqInstructionArgumentNames; program: string }
| { type: "GetDocsListedByVar"; var_name: string }
| { type: "GetLatestCallerExternalId" }
| { type: "GetLatestCallerExternalId"; trusted_author: AgentPubKey }
| { type: "GetCallerAgentPublicKey" };

0 comments on commit e405c75

Please sign in to comment.