Skip to content
This repository was archived by the owner on Sep 30, 2023. It is now read-only.

Refactor access controller#128

Closed
haadcode wants to merge 5 commits into
masterfrom
feat/acl-refactor
Closed

Refactor access controller#128
haadcode wants to merge 5 commits into
masterfrom
feat/acl-refactor

Conversation

@haadcode

Copy link
Copy Markdown
Member

This PR refactors the access controller in a way that the ACL handles all keys, signing and verification related functionality. The general idea is that Log/Entry don't know anything about the keys or keystore and only gets and acl object (the access controller) which encapsulates the functionality.

I did the previous commits in a bit of a rush, so there may be some weirdness here. Would love to hear your feedback @coyotespike, @shamb0t and @vvp.

I'm unsure if the ACL api is what we want to do in the end, but at least this would give us a "clean" separation of the acl and the log.

I'll try to check this again myself in the next couple of weeks to make sure everything makes sense, but for now, I hope this helps you to proceed with the refactoring / acl work that you're doing.

fazo96 and others added 3 commits April 24, 2018 12:05
* added docs and tests for custom verification
* fix sync join calls in replication test
* added verifyEntry constructor argument
* add verifyEntry param to Factory functions. Don't use setter for it
@haadcode

Copy link
Copy Markdown
Member Author

@fazo96 would love to hear your feedback too on this. Your PR #123 has been open for a long time (sorry about that! :/) and this one is based on yours but with a very different code as to how to do the custom verification. I believe this would still give you that functionality but with the difference that instead of passing a custom verification function, you'd have to write a custom access controller (which I think would make sense generally if you're planning to check the ACL against a smart contract).

@fazo96

fazo96 commented Jun 25, 2018

Copy link
Copy Markdown

hey @haadcode, thanks for involving me in this :)

I think this ACL refactor looks much better designed than my PR and will get me the same functionality.

Also thrilled by how easy it looks to pass a custom key to the ACL 👍

One thing I can't wrap my head around is what happens if an ACL is not passed? No more signing of the entries by default? I'm probably just missing something

@haadcode

Copy link
Copy Markdown
Member Author

One thing I can't wrap my head around is what happens if an ACL is not passed? No more signing of the entries by default? I'm probably just missing something

I left out that part and we should discuss what the default behaviour should be. You're correct that right now the entries don't get signed if an acl is not passed (which prolly doesn't have a lot of use cases as it breaks the integrity of the log). I suppose we could either throw an error or defer to a default access controller (like it used to be / is now). Any thoughts?

@fazo96 do you have any links to how you're using the custom verification in your code? Would love to understand your use case better so that we can take that into consideration designing all this.

@fazo96

fazo96 commented Jun 25, 2018

Copy link
Copy Markdown

@haadcode sure, here is the custom OrbitDB Store we use in the current Chlu implementation:

https://github.com/ChluNetwork/chlu-ipfs-support/blob/master/src/modules/orbitdb/store.js

For our validation needs, there's three points at which we can validate content before accepting it:

  1. before joining logs (using either my PR Custom verification and custom signing #127 or this PR and a custom ACL)
  2. in the updateIndex function, so we don't put invalid data in the index
  3. when reading from the index
  4. after reading from the index

We haven't much validation going on related to orbit-db data, but the ReviewRecords we store are supposed to be independently validatable objects and they are, so the validation for now occures in the code that comes after reading from the orbit-db index (point 4).

This is not ideal because the index can get polluted by any fake information, and also we allow review records to be updated and the current implementation makes it possible for anyone to create a bogus update and thus making the review record invalid for everyone.

The point of my PR #127 was to make point 1 possible and orbitdb-archive/orbit-db-store#21 already made point 2 possible. point 3 is easy because we have a custom index and point 4 does not involve orbit-db.

So we have an external, async validation function that can be called with the CID of the review record (passed in the payload of the orbit-db operation). This function will return true if the data is valid and should be kept in orbit-db.

In conclusion, our use case was making sure we have the means of keeping invalid data out of the index or even out of the oplog, depends on the user/application of our Chlu protocol (maybe it is preferable to keep invalid data in the oplog for further analysis and debugging, for example, but still keep it out of the index).

Another important point in favour is that to authenticate users we use a Decentralized Identifier (DID) that has its own keypair and the custom ACL in this PR makes it easier for us to reuse DID keypairs for orbit-db signing and validating.

@coyotespike coyotespike left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This PR makes log.js so much cleaner! Nice! I have a questions about append, noted below, and will think about how this interacts with other PRs.

Comment thread src/log.js Outdated
* @return {Promise<Log>} New Log
*/
static fromEntry (ipfs, sourceEntries, length = -1, exclude, onProgressCallback) {
static fromEntry (ipfs, sourceEntries, length = -1, exclude, onProgressCallback, verifyEntry) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Log no longer takes verifyEntry, and so neither should fromEntry. That argument can be removed.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you! 👍Will fix all the verifyEntry args as per your comments.

Comment thread src/log.js Outdated
// TODO: need to verify the entries with 'key'
return LogIO.fromEntry(ipfs, sourceEntries, length, exclude, onProgressCallback)
.then((data) => new Log(ipfs, data.id, data.values))
.then((data) => new Log(ipfs, data.id, data.values, null, null, null, verifyEntry))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

And same here, Log now takes six arguments, with the seventh being an ACL.

Comment thread src/log.js Outdated
* @return {Promise<Log>} New Log
*/
static fromMultihash (ipfs, hash, length = -1, exclude, key, onProgressCallback) {
static fromMultihash (ipfs, hash, length = -1, exclude, acl, onProgressCallback, verifyEntry) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js Outdated
* @param {string} hash Multihash (as a Base58 encoded string) to create the log from
* @param {Number} [length=-1] How many items to include in the log
* @param {Function(hash, entry, parent, depth)} onProgressCallback
* @param {Function} [verifyEntry] Custom entry verification function

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js Outdated
// TODO: need to verify the entries with 'key'
return LogIO.fromMultihash(ipfs, hash, length, exclude, onProgressCallback)
.then((data) => new Log(ipfs, data.id, data.values, data.heads, data.clock, key))
.then((data) => new Log(ipfs, data.id, data.values, data.heads, data.clock, acl, verifyEntry))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js Outdated
* @return {Promise<Log>} New Log
*/
static fromEntryHash (ipfs, hash, id, length = -1, exclude, key, keys, onProgressCallback) {
static fromEntryHash (ipfs, hash, id, length = -1, exclude, acl, onProgressCallback, verifyEntry) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js Outdated
* @param {string} hash Multihash (as a Base58 encoded string) of the Entry from which to create the log from
* @param {Number} [length=-1] How many entries to include in the log
* @param {Function(hash, entry, parent, depth)} onProgressCallback
* @param {Function} [verifyEntry] Custom entry verification function

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js Outdated
* @return {Promise<Log>} New Log
*/
static fromJSON (ipfs, json, length = -1, key, keys, timeout, onProgressCallback) {
static fromJSON (ipfs, json, length = -1, acl, timeout, onProgressCallback, verifyEntry) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js Outdated
return LogIO.fromJSON(ipfs, json, length, key, timeout, onProgressCallback)
.then((data) => new Log(ipfs, data.id, data.values, null, null, key, keys))
return LogIO.fromJSON(ipfs, json, length, timeout, onProgressCallback)
.then((data) => new Log(ipfs, data.id, data.values, null, null, acl, verifyEntry))

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

verifyEntry not necessary

Comment thread src/log.js
@@ -198,20 +200,13 @@ class Log extends GSet {
* @return {Log} New Log containing the appended value
*/
async append (data, pointerCount = 1) {

@coyotespike coyotespike Jun 26, 2018

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[edit: this comment previously worried that we weren't checking for write permissions in append, but now I see that Entry.create calls sign which calls canAppend 👍 ]

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Your previous comment was a good point though. I was thinking if we should make it (calling canAppend or sign) explicit and call it in the log instead of in Entry?

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I already forgot it a second time 😂 so we could either make it explicit, or make a generalized testing suite for the access controller API to ensure that all access controllers conform to this API.

@coyotespike

Copy link
Copy Markdown

ipfs-log just takes an acl - so it would seem that we can still pass an acl down from orbit-db through orbit-db-store.

Do you intend acl.js to move to orbit-db? I am curious about the split of responsibilities. The access controllers on the dynamic permissions branch are setting and getting capabilities. acl.js uses those keys, and the keystore, to check permissions with sign and verify etc.

The smart contract access controller needs to do both, because it can't provide a list of keys. I suppose we'll just pass down the smart contract access controller.

Comment thread src/entry.js

static async signEntry (keystore, entry, key) {
const signature = await keystore.sign(key, Buffer.from(JSON.stringify(entry)))
static async sign (entry, acl) {

@coyotespike coyotespike Jul 3, 2018

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A smart contract access controller will want to augment the entry with a chainSignature and chainPublicKey, or something like that.

So perhaps acl.sign should return the entry, and the default acl is responsible for adding the key and signature.

Comment thread src/acl.js
@@ -0,0 +1,53 @@
class ACL {
constructor (keystore, key, keys = []) {
this._keystore = keystore

@thiagodelgado111 thiagodelgado111 Jul 6, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think here it should only get sign, getPubKey and verify functions instead of direct access to the keys. We should keep sign, getPubKey and verify here though so we can handle encoding/parsing on ipfs-log

Comment thread src/acl.js
@@ -0,0 +1,53 @@
class ACL {

@thiagodelgado111 thiagodelgado111 Jul 6, 2018

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Maybe we could call it IntegrityChecker to avoid confusion around orbit-db vs ipfs-log ACL

Comment thread src/acl.js
return this._key.getPublic(format)
}

canAppend (signingKey) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If we pass functions from orbit-db access controller to it, we wouldn't need this anymore

Comment thread src/log.js
const nexts = Object.keys(this.traverse(this.heads, pointerCount))
// Create the entry and add it to the internal cache
const entry = await Entry.create(this._storage, this._keystore, this.id, data, nexts, this.clock, this._key)
const entry = await Entry.create(this._storage, this._acl, this.id, data, nexts, this.clock)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Instead of passing the whole ACL object here, the Entry.create method could be broken up into create/sign/put. It might make it easier to understand how it actually works – since one might assume this is simply creating an entry instance – and then we can pass just the public key to be used as a fallback for the clockId property. Maybe a TODO for another PR?

Comment thread src/acl.js
return this._key
}

getPublicSigningKey (format = 'hex') {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think ipfs-log always uses this format. Is it going to change or can we assume it'll always be 'hex'?

Comment thread src/log.js
return entries.reduce(reduceTailHashes, [])
}

static difference (a, b) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is it similar to utils/difference?

Comment thread src/log.js
this._key = key
this._keys = Array.isArray(keys) ? keys : [keys]
// Access Controller
this._acl = acl

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Like @fazo96 mentioned in his comment (#128 (comment)), maybe we should add some validation here that checks if the ACL exists and if it follows the "interface" we need. If it doesn't, we throw an error. Is there a scenario where it wouldn't be necessary?

Comment thread src/entry.js
if (keystore && signKey) {
entry = await Entry.signEntry(keystore, entry, signKey)
// If acl was passed, sign the entry (=authorize)
if (acl) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think this should be a breaking change, providing an ACL should be mandatory from now on

@haadcode

haadcode commented Sep 3, 2018

Copy link
Copy Markdown
Member Author

Closing as obsolete in favor of #159.

@haadcode haadcode closed this Sep 3, 2018
@haadcode haadcode deleted the feat/acl-refactor branch September 4, 2018 10:51
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants