Probably the best way to think about this is not so much that certain agents are permitted/rejected re: calling approve_article() but that the side effects of approve_article() are permitted/rejected. That is, any entry that gets written as a result of calling approve_article() passes or fails validation based on the agent ID. This is because, in a distributed system where we canât guarantee that our peers are running the right code (even if they have proof that they are), all we can do is validate the data they produce. This is true for blockchain as well.
Validation should depend entirely on the contents of the entry itself + the contents of the validation package. It should yield the same result regardless of whether itâs being processed by the author, a counterparty, or a third-party validator. Therefore:
We canât use globals that vary by location or time such as hdk::AGENT_ADDRESS or the local clock.
We canât check the current state of the DHT, because itâs always changing. That means that any proof that the author has the right to publish an entry should be part of the entry â usually as a signature of someone who has the power to grant that right.
I realise this is a complex topic, and weâre working on some introductory literature about how to implement this⌠but itâs not going to be ready for a month or so But itâs very similar to how SSL certificates work. Hereâs a table that shows the parallels:
SSL certificate infrastructure
Certificates in Holochain app
Certificates are documents, signed by an authority, that outlining the authority being granted
Same
CA root authority, trusted by browser
Root admin user, trusted explicitly by DNA
Enforced by browser
Enforced by end-user and validators
This isnât something supported by the HDK, but it should be fairly straightforward to implement this with app-level primitives:
The DNA specifies the root authority in the properties section of app.json â this would probably look like a public key that all users should recognise as the admin userâs public key.
The root authority authorises other people to do things by specifying their privileges in a certificate. Hereâs a simple example of what that might look like:
pub enum Privilege {
AuthorArticles,
ApproveArticles,
DeleteArticles,
// ... etc
}
pub struct PrivilegeCertificateBody {
/// What's the privilege being granted?
privilege: Privilege,
/// The 'subject' is the person who's being given a privilege. This
/// looks like their agent ID.
subject: Address,
/// Just as with SSL certs, privileges should have an expiry in case
/// you want to revoke that privilege later. A subject should always
/// produce a certificate that's currently valid, and an authority
/// should always create a new certificate for the subject when they
/// ask, unless the authority has reason to revoke that privilege.
expires: Option<DateTime>,
}
pub struct PrivilegeCertificate {
/// This is the thing being signed.
body: PrivilegeCertificateBody,
/// This is the authority's signature on the body, along with their
/// public key, which proves that they've granted this authority.
provenance: holochain_core_types::signature::Provenance,
}
Any action that requires privileges should include a reference to the certificate that grants the privilege. In the future, youâll be able to do that by specifying the hash of the certificate:
The validation rules for an action (such as article approval) should check the validity of the certificate:
Its public key should be an authority recognised by the app.
Its signature should actually be valid for the public key and PrivilegeCertificateBody (only strings can be signed, so the body should be JSON-ified before signing).
Its subject and granted privilege should match the action that the agent is trying to take.
In the future, when youâre able to reference the certificate by hash, Holochain will automatically perform validation on that certificate before performing validation on the article approval action. Then, for validating the article approval entry, youâll only have to check condition 3 in the list above (subject and granted privilege match the action being performed).
In validation: |validation_data: hdk::EntryValidationData<Order>| { ... } is there a way to know what function is in charge for calling this? Say, it might be my custom function âmy_custom_update_entity(âŚ)â or âset_entity_status(âŚ)â, or âdelete_entity(âŚ)â etc
In other words, when an agent is calling âmy_custom_update(âŚ)â which is supposed to update an Entity, I want to be able to retrieve its name â âmy_custom_updateâ â in
âvalidationâ. And then allow or reject the actual update operation.
Validation is a call back when you attempt to change an entry, it does not track which function updated it nor does it track the changes to the entry.
For what you have described Iâd probably try to add that feature into the âmy_custom_updateâ function not the validation.
@alx may I ask why the validation function needs to know what function created the entry itâs trying to validate? You could put a field into the entry like called_from: String, but thatâs easy for any malicious agent to fake, so you canât rely on it being true. And to reiterate about context in a validation function:
A validation function should always be stateless. We canât be sure where a validation function is going to be called, and thatâs by design â the result shouldnât depend on whoâs running the validation. It should always be either true or false based on the entry and the validation package alone. Thatâs why the DHT itself should enforce rights to edit an entry or specific fields in an entry.
Thoughts on updating certain fields: regardless of how you authorise a certain agent to edit such-and-such a field, what you could do is compare the updated entry to the original â if any field has changed in the updated entry, and the agent isnât allowed to update that field, then validation fails. The original can be found in EntryValidationData::Modify { old_entry }
. Then the problem is reformulated from
âagent A may only edit fields X and Y of entry type Eâ
to
âwhen agent A creates an updated entry for entry type E, they must leave all fields unchanged except X and Yâ
Regarding how to authorize, itâd help us if you mapped out your use case in more detail â exactly who should be allowed to update an entry (only the original author, only the official âadminâ, a mutable group of privileged people, etc). Could you give us a clearer picture of your authorisation needs?
That will work. You can also put their Ids in the properties section of the dna.json file. Iâm just doing an example of that right now and will post here later.
Other thing to think about: Is there a way you can achieve your goal without having old school ideas like admins and approvers? In an agent centric world you can use membranes (doing an example of this too) and signatures to do things like approve. Imagine a setup where you have the âopenâ DHT where unapproved articles get published and another DHT that has the âapprovedâ articles. Then you setup a tighter membrane on the âapprovedâ DHT and only allow those agents to bridge between them and âapproveâ the article frpm the open DHT to the approved DHT.
#[validate_agent]
pub fn validate_agent(validation_data: EntryValidationData<AgentId>) {
if let EntryValidationData::Create{entry, ..} = validation_data {
let properties = hdk::api::property("allowed_members");
if let Ok(members) = properties {
let member_list: Vec<String> = serde_json::from_str(&members.to_string()).unwrap();
hdk::debug(format!("PROPERTIES: {:?}", member_list)).ok();
let agent = entry as AgentId;
hdk::debug(format!("AgentId: {:?}", agent)).ok();
if member_list.contains(&agent.pub_sign_key) {
Ok(())
} else {
Err("This agent is not in the allowed members list".into())
}
} else {
Err("Issue reading members from dna.json".into())
}
} else {
hdk::debug(format!("Cannot update or delete an agent at this time")).ok();
Err("Cannot update or delete an agent at this time".into())
}
}
It should always be either true or false based on the entry, the validation package, or static values alone.
Therefore, "MY_MAIN_AGENT_ADDRESS" is okay because itâs a string literal, and @philipbeadleâs example of embedding allowed addresses in the DNAâs properties is super super versatile.
You can specify a properties object in app.json as you guessed. You can also add to those properties at installation time â check out the conductor admin API (search for admin/dna/install_from_file). It lets you interpolate new values into your properties object, so you can use one base DNA as a template and create new DNAs, each with their own main agent address value. That lets you create multiple little spaces, each of which has its own admin. This could be useful for having individual public blogs where only the blog owner gets to post articles.