Skip to content

Commit

Permalink
Search for keys
Browse files Browse the repository at this point in the history
  • Loading branch information
ThetaSinner committed Feb 17, 2024
1 parent 32ed796 commit 07d9097
Show file tree
Hide file tree
Showing 7 changed files with 205 additions and 44 deletions.
107 changes: 79 additions & 28 deletions dnas/trusted/zomes/coordinator/trusted/src/gpg_key_dist.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,39 +9,64 @@ pub struct DistributeGpgKeyRequest {
}

#[hdk_extern]
pub fn distribute_gpg_key(gpg_key: DistributeGpgKeyRequest) -> ExternResult<Record> {
let public_key = try_extract_public_key(gpg_key.public_key.clone())?;
pub fn distribute_gpg_key(request: DistributeGpgKeyRequest) -> ExternResult<Record> {
let public_key = try_extract_public_key(request.public_key.clone())?;

let summary = PublicKeySummary::try_from_public_key(&public_key)?;

let has_key = get_my_keys(())?.iter().find(|record| {
match record.entry.as_option() {
let has_key = get_my_keys(())?
.iter()
.find(|record| match record.entry.as_option() {
Some(Entry::App(app_entry)) => {
let gpg_key_dist: GpgKeyDist = app_entry.clone().into_sb().try_into().unwrap();
gpg_key_dist.fingerprint == summary.fingerprint
}
_ => false,
}
}).is_some();
})
.is_some();

if has_key {
return Err(wasm_error!(WasmErrorInner::Guest(
"You have already distributed this key".to_string()
)));
}

let gpg_key_hash = create_entry(&EntryTypes::GpgKeyDist(GpgKeyDist {
public_key: gpg_key.public_key,
fingerprint: summary.fingerprint,
user_id: summary.user_id,
email: summary.email,
let gpg_key_dist_hash = create_entry(&EntryTypes::GpgKeyDist(GpgKeyDist {
public_key: request.public_key,
fingerprint: summary.fingerprint.clone(),
user_id: summary.user_id.clone(),
email: summary.email.clone(),
}))?;
let record = get(gpg_key_hash.clone(), GetOptions::default())?
.ok_or(
wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly created GpgKey"))
),

let record = get(gpg_key_dist_hash.clone(), GetOptions::default())?.ok_or(wasm_error!(
WasmErrorInner::Guest(String::from("Could not find the newly created GpgKey"))
))?;

let entry_hash = record.action().entry_hash().ok_or_else(|| wasm_error!(WasmErrorInner::Guest(String::from("Missing entry hash"))))?;

create_link(
make_base_hash(summary.user_id)?,
entry_hash.clone(),
LinkTypes::UserIdToGpgKeyDist,
(),
)?;

if let Some(email) = summary.email {
create_link(
make_base_hash(email)?,
entry_hash.clone(),
LinkTypes::EmailToGpgKeyDist,
(),
)?;
}

create_link(
make_base_hash(summary.fingerprint)?,
entry_hash.clone(),
LinkTypes::FingerprintToGpgKeyDist,
(),
)?;

Ok(record)
}

Expand All @@ -57,19 +82,45 @@ pub fn get_my_keys(_: ()) -> ExternResult<Vec<Record>> {
Ok(gpg_key_dist_entries)
}

#[derive(Serialize, Deserialize, Debug, Clone, SerializedBytes)]
pub struct SearchKeysRequest {
pub query: String,
}

#[hdk_extern]
pub fn get_gpg_key_dist(gpg_key_hash: ActionHash) -> ExternResult<Option<Record>> {
let Some(details) = get_details(gpg_key_hash, GetOptions::default())? else {
return Ok(None);
};
match details {
Details::Record(details) => Ok(Some(details.record)),
_ => {
Err(
wasm_error!(
WasmErrorInner::Guest(String::from("Malformed get details response"))
),
)
pub fn search_keys(request: SearchKeysRequest) -> ExternResult<Vec<Record>> {
let mut links = get_links(GetLinksInputBuilder::try_new(make_base_hash(request.query.clone())?, LinkTypes::UserIdToGpgKeyDist)?.build())?;
let email_links = get_links(GetLinksInputBuilder::try_new(make_base_hash(request.query.clone())?, LinkTypes::EmailToGpgKeyDist)?.build())?;
let fingerprint_links = get_links(GetLinksInputBuilder::try_new(make_base_hash(request.query)?, LinkTypes::FingerprintToGpgKeyDist)?.build())?;

links.extend(email_links);
links.extend(fingerprint_links);

let mut out = Vec::with_capacity(links.len());
for target in links.into_iter().flat_map(|l| AnyDhtHash::try_from(l.target).ok()) {
match get(target, GetOptions::default())? {
Some(r) => {
out.push(r);
}
_ => {
// Link target not found
}
}
}

Ok(out)
}

fn make_base_hash(input: String) -> ExternResult<EntryHash> {
hash_entry(Entry::App(
AppEntryBytes::try_from(SerializedBytes::from(UnsafeBytes::from(
input.as_bytes().to_vec(),
)))
.map_err(|e| {
wasm_error!(WasmErrorInner::Guest(format!(
"Cannot create base hash from {}: {}",
input, e
)))
})?,
))
}
49 changes: 48 additions & 1 deletion dnas/trusted/zomes/integrity/trusted/src/gpg_key_dist.rs
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
use hdi::prelude::*;
use hdi::prelude::{hash_type::AnyLinkable, *};
use crate::LinkTypes;

#[hdk_entry_helper]
#[derive(Clone, PartialEq)]
pub struct GpgKeyDist {
Expand Down Expand Up @@ -31,3 +33,48 @@ pub fn validate_delete_gpg_key_dist(
) -> ExternResult<ValidateCallbackResult> {
Ok(ValidateCallbackResult::Invalid(String::from("Gpg key distributions cannot be deleted")))
}

pub fn validate_create_gpg_key_dist_link(
base_address: HoloHash<AnyLinkable>,
target_address: HoloHash<AnyLinkable>,
link_type: LinkTypes,
) -> ExternResult<ValidateCallbackResult> {
let entry_hash = match target_address.clone().try_into() {
Ok(entry_hash) => entry_hash,
Err(e) => {
return Ok(
ValidateCallbackResult::Invalid(
format!("The target address for {:?} must be an entry hash", link_type),
),
);
}
};
let entry = must_get_entry(entry_hash)?;
match entry.as_app_entry() {
Some(app_entry) => {
let _: crate::gpg_key_dist::GpgKeyDist = match app_entry.clone().into_sb().try_into() {
Ok(gpg_key) => gpg_key,
Err(e) => {
return Ok(
ValidateCallbackResult::Invalid(
format!("The target for {:?} must be a {}", link_type, std::any::type_name::<crate::gpg_key_dist::GpgKeyDist>()),
),
);
}

};
}
None => {
return Ok(
ValidateCallbackResult::Invalid(
format!("The target for {:?} must be an app entry", link_type),
),
);

}
}

Ok(
ValidateCallbackResult::Valid,
)
}
35 changes: 23 additions & 12 deletions dnas/trusted/zomes/integrity/trusted/src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
mod gpg_key_dist;
pub(crate) mod gpg_key_dist;

use std::collections::linked_list;

use hdi::prelude::*;
use prelude::validate_create_gpg_key_dist_link;

pub mod prelude {
pub use crate::gpg_key_dist::*;
pub use crate::{EntryTypes, UnitEntryTypes};
pub use crate::LinkTypes;
}

#[derive(Serialize, Deserialize)]
Expand All @@ -15,6 +19,13 @@ pub enum EntryTypes {
GpgKeyDist(gpg_key_dist::GpgKeyDist),
}

#[hdk_link_types]
pub enum LinkTypes {
UserIdToGpgKeyDist,
EmailToGpgKeyDist,
FingerprintToGpgKeyDist,
}

// Validation you perform during the genesis process. Nobody else on the network performs it, only you.
// There *is no* access to network calls in this callback
#[hdk_extern]
Expand Down Expand Up @@ -55,7 +66,7 @@ pub fn validate_agent_joining(
// You can read more about validation here: https://docs.rs/hdi/latest/hdi/index.html#data-validation
#[hdk_extern]
pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
match op.flattened::<EntryTypes, ()>()? {
match op.flattened::<EntryTypes, LinkTypes>()? {
FlatOp::StoreEntry(store_entry) => {
match store_entry {
OpEntry::CreateEntry { app_entry, action } => {
Expand Down Expand Up @@ -133,11 +144,11 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
tag,
action,
} => {
Ok(
ValidateCallbackResult::Invalid(
String::from("There are no link types in this integrity zome"),
),
)
match link_type {
LinkTypes::FingerprintToGpgKeyDist | LinkTypes::UserIdToGpgKeyDist | LinkTypes::EmailToGpgKeyDist => {
validate_create_gpg_key_dist_link(base_address, target_address, link_type)
}
}
}
FlatOp::RegisterDeleteLink {
link_type,
Expand Down Expand Up @@ -299,11 +310,11 @@ pub fn validate(op: Op) -> ExternResult<ValidateCallbackResult> {
link_type,
action,
} => {
Ok(
ValidateCallbackResult::Invalid(
"There are no link types in this integrity zome".to_string(),
),
)
match link_type {
LinkTypes::FingerprintToGpgKeyDist | LinkTypes::UserIdToGpgKeyDist | LinkTypes::EmailToGpgKeyDist => {
validate_create_gpg_key_dist_link(base_address, target_address, link_type)
}
}
}
// 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`
Expand Down
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"name": "hWOT-dev",
"name": "hwot-dev",
"private": true,
"workspaces": [
"ui",
Expand All @@ -9,7 +9,7 @@
"start": "AGENTS=2 BOOTSTRAP_PORT=$(port) SIGNAL_PORT=$(port) npm run network",
"network": "hc s clean && npm run build:happ && UI_PORT=8888 concurrently \"npm start -w ui\" \"npm run launch:happ\" \"holochain-playground\"",
"test": "npm run build:zomes && hc app pack workdir --recursive && npm t -w tests",
"launch:happ": "concurrently \"hc run-local-services --bootstrap-port $BOOTSTRAP_PORT --signal-port $SIGNAL_PORT\" \"echo pass | RUST_LOG=warn hc launch --piped -n $AGENTS workdir/hWOT.happ --ui-port $UI_PORT network --bootstrap http://127.0.0.1:\"$BOOTSTRAP_PORT\" webrtc ws://127.0.0.1:\"$SIGNAL_PORT\"\"",
"launch:happ": "concurrently \"hc run-local-services --bootstrap-port $BOOTSTRAP_PORT --signal-port $SIGNAL_PORT\" \"echo pass | RUST_LOG=warn WASM_LOG=info hc launch --piped -n $AGENTS workdir/hWOT.happ --ui-port $UI_PORT network --bootstrap http://127.0.0.1:\"$BOOTSTRAP_PORT\" webrtc ws://127.0.0.1:\"$SIGNAL_PORT\"\"",
"package": "npm run build:happ && npm run package -w ui && hc web-app pack workdir --recursive",
"build:happ": "npm run build:zomes && hc app pack workdir --recursive",
"build:zomes": "RUSTFLAGS='' CARGO_TARGET_DIR=target cargo build --release --target wasm32-unknown-unknown"
Expand Down
6 changes: 5 additions & 1 deletion ui/src/App.vue
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@
<CreateGpgKey @gpg-key-dist-created="() => {}"></CreateGpgKey>

<MyKeys></MyKeys>

<SearchKeys></SearchKeys>
</div>
</div>
</div>
Expand All @@ -19,11 +21,13 @@ import '@material/mwc-circular-progress';
import '@material/mwc-button';
import CreateGpgKey from './trusted/trusted/CreateGpgKey.vue';
import MyKeys from './trusted/trusted/MyKeys.vue';
import SearchKeys from './trusted/trusted/SearchKeys.vue';
export default defineComponent({
components: {
CreateGpgKey,
MyKeys
MyKeys,
SearchKeys
},
data(): {
client: AppAgentClient | undefined;
Expand Down
44 changes: 44 additions & 0 deletions ui/src/trusted/trusted/SearchKeys.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
<script setup lang="ts">
import { AppAgentClient, Record } from '@holochain/client';
import { ComputedRef, inject, ref, watch } from 'vue';
import { GpgKeyDist, SearchKeysRequest } from './types';
import { decode, encode } from '@msgpack/msgpack';
const searchQuery = ref('');
const results = ref<GpgKeyDist[]>([]);
const client = inject('client') as ComputedRef<AppAgentClient>;
const searchKeys = async () => {
if (!client.value) return;
const request: SearchKeysRequest = {
query: searchQuery.value,
}
const r: Record[] = await client.value.callZome({
role_name: 'trusted',
zome_name: 'trusted',
fn_name: 'search_keys',
payload: request,
cap_secret: null,
});
results.value = r.map((record) => {
console.log('got record', record);
return decode((record.entry as any).Present.entry) as GpgKeyDist
})
}
</script>

<template>
<label for="search-for-keys">Search for keys by user id, email or fingerprint</label>
<input type="text" name="search-for-keys" id="search-for-keys" v-model="searchQuery" />
<mwc-button raised label="Search" @click="searchKeys"></mwc-button>

<div v-for="r in results" v-bind:key="r.fingerprint">
<div>{{ r.fingerprint }}</div>
<div>{{ r.user_id }}</div>
</div>
</template>
4 changes: 4 additions & 0 deletions ui/src/trusted/trusted/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,3 +48,7 @@ export interface GpgKeyDist {
user_id: string;
email?: string;
}

export interface SearchKeysRequest {
query: string;
}

0 comments on commit 07d9097

Please sign in to comment.