Skip to content

Commit

Permalink
Remove key from collection without validation
Browse files Browse the repository at this point in the history
  • Loading branch information
ThetaSinner committed Feb 28, 2024
1 parent e6b78ab commit f41c98c
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 7 deletions.
102 changes: 101 additions & 1 deletion dnas/trusted/zomes/coordinator/trusted/src/key_collection.rs
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,6 @@ pub struct LinkGpgKeyToKeyCollectionRequest {
pub key_collection_name: String,
}

// TODO prevent duplicate links? Could also be done in the UI fairly easily
#[hdk_extern]
pub fn link_gpg_key_to_key_collection(
request: LinkGpgKeyToKeyCollectionRequest,
Expand Down Expand Up @@ -147,6 +146,107 @@ pub fn link_gpg_key_to_key_collection(
)
}

#[derive(Serialize, Deserialize, Debug, Clone, SerializedBytes)]
pub struct UnlinkGpgKeyFromKeyCollectionRequest {
pub gpg_key_fingerprint: String,
pub key_collection_name: String,
}

#[hdk_extern]
pub fn unlink_gpg_key_from_key_collection(
request: UnlinkGpgKeyFromKeyCollectionRequest,
) -> ExternResult<()> {
let fingerprint_links = get_links(
GetLinksInputBuilder::try_new(
make_base_hash(&request.gpg_key_fingerprint)?,
LinkTypes::FingerprintToGpgKeyDist,
)?
.build(),
)?;

if fingerprint_links.is_empty() {
return Err(wasm_error!(WasmErrorInner::Guest(String::from(
"No GPG key found with the given fingerprint"
))));
}

if fingerprint_links.len() > 1 {
return Err(wasm_error!(WasmErrorInner::Guest(String::from(
"Multiple GPG keys found with the given fingerprint"
))));
}

// Target is the entry hash of the GpgKeyDist
let fingerprint_link = fingerprint_links[0].clone();

let my_key_collections = inner_get_my_key_collections()?;

let matched_collection = my_key_collections.into_iter().find(|r| {
r.entry
.as_option()
.and_then(|e| e.as_app_entry())
.and_then(|e| {
let key_collection: KeyCollection = e.clone().into_sb().try_into().ok()?;
Some(key_collection.name == request.key_collection_name)
})
.unwrap_or(false)
});

let key_collection = matched_collection.ok_or(wasm_error!(WasmErrorInner::Guest(
String::from("No key collection found with the given name")
)))?;

let agent_info = agent_info()?;

let potential_links_from_selected_collection = get_links(
GetLinksInputBuilder::try_new(
key_collection.action_hashed().as_hash().clone(),
LinkTypes::KeyCollectionToGpgKeyDist.try_into_filter()?,
)?
.author(agent_info.agent_initial_pubkey.clone())
.build(),
)?;

// Unlink the the key collection from the GpgKeyDist
let mut removing_tags = HashSet::new();
for link in potential_links_from_selected_collection.into_iter() {
// Find the links from this collection that target the key to remove
if link.target == fingerprint_link.target.clone() {
removing_tags.insert(link.tag);
delete_link(link.create_link_hash)?;
}
}

let potential_links_from_fingerprint = get_links(
GetLinksInputBuilder::try_new(
fingerprint_link.target,
LinkTypes::GpgKeyDistToKeyCollection.try_into_filter()?,
)?
.author(agent_info.agent_initial_pubkey)
.build(),
)?;

// Unlink the key fingerprint from the key collection
for link in potential_links_from_fingerprint.into_iter() {
// Find the links from this collection that target the key to remove
if link.target == key_collection.action_hashed().as_hash().clone().into() {
if !removing_tags.remove(&link.tag) {
return Err(wasm_error!(WasmErrorInner::Guest(format!(
"Link from fingerprint to key collection has tag {:?} but no corresponding link from key collection to fingerprint was deleted",
link.tag
))));
}
delete_link(link.create_link_hash)?;
}
}

if !removing_tags.is_empty() {
tracing::warn!("There were links from the key collection that did not correspond to a link from the key fingerprint. Validation is supposed to prevent this. {:?}", removing_tags);
}

Ok(())
}

/// Checks if the key collection can be created.
///
/// - Ensures the name is at least [KEY_COLLECTION_NAME_MIN_LENGTH] characters long. Also checked by validation.
Expand Down
10 changes: 4 additions & 6 deletions dnas/trusted/zomes/integrity/trusted/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,9 +161,9 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
)
}
},
FlatOp::RegisterDeleteLink { .. } => Ok(ValidateCallbackResult::Invalid(String::from(
"There are no link types in this integrity zome",
))),
FlatOp::RegisterDeleteLink { original_action, link_type, tag, .. } => match link_type {
_ => Ok(ValidateCallbackResult::Valid)
},
FlatOp::StoreRecord(store_record) => {
match store_record {
// Complementary validation to the `StoreEntry` Op, in which the record itself is validated
Expand Down Expand Up @@ -337,9 +337,7 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
// Complementary validation to the `RegisterDeleteLink` Op, in which the record itself is validated
// If you want to optimize performance, you can remove the validation for an entry type here and keep it in `RegisterDeleteLink`
// Notice that doing so will cause `must_get_valid_record` for this record to return a valid record even if the `RegisterDeleteLink` validation failed
OpRecord::DeleteLink { .. } => Ok(ValidateCallbackResult::Invalid(
"There are no link types in this integrity zome".to_string(),
)),
OpRecord::DeleteLink { .. } => Ok(ValidateCallbackResult::Valid),
OpRecord::CreatePrivateEntry { .. } => Ok(ValidateCallbackResult::Valid),
OpRecord::UpdatePrivateEntry { .. } => Ok(ValidateCallbackResult::Valid),
OpRecord::CreateCapClaim { .. } => Ok(ValidateCallbackResult::Valid),
Expand Down
56 changes: 56 additions & 0 deletions tests/src/trusted/trusted/key-collection.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,6 +122,62 @@ test("Link GPG key to collection", async () => {
});
});

test("Unlink GPG key from collection", async () => {
await runScenario(async (scenario) => {
const testAppPath = process.cwd() + "/../workdir/hWOT.happ";
const appSource = { appBundleSource: { path: testAppPath } };

const [alice] = await scenario.addPlayersWithApps([appSource]);

// Alice distributes a GPG key
const gpg_key_record: Record = await distributeGpgKey(
alice.cells[0],
sampleGpgKey(),
);
assert.ok(gpg_key_record);

// Alice creates a key collection
const key_collection_record: Record = await createKeyCollection(
alice.cells[0],
"a test",
);
assert.ok(key_collection_record);

// Alice links the GPG key to the key collection
await alice.cells[0].callZome({
zome_name: "trusted",
fn_name: "link_gpg_key_to_key_collection",
payload: {
gpg_key_fingerprint:
decodeRecord<GpgKeyDist>(gpg_key_record).fingerprint,
key_collection_name: "a test",
},
});

// Alice unlinks the GPG key from the key collection
await alice.cells[0].callZome({
zome_name: "trusted",
fn_name: "unlink_gpg_key_from_key_collection",
payload: {
gpg_key_fingerprint:
decodeRecord<GpgKeyDist>(gpg_key_record).fingerprint,
key_collection_name: "a test",
},
});

// Now getting key collections should return a single, empty key collection
const key_collections: KeyCollectionWithKeys[] =
await alice.cells[0].callZome({
zome_name: "trusted",
fn_name: "get_my_key_collections",
payload: null,
});

assert.equal(key_collections.length, 1);
assert.equal(key_collections[0].gpg_keys.length, 0);
});
});

test("Remote validation", async () => {
await runScenario(async (scenario) => {
const testAppPath = process.cwd() + "/../workdir/hWOT.happ";
Expand Down

0 comments on commit f41c98c

Please sign in to comment.