Refactor/ac (#61)

* refactor: Move Manifest to own module.

* refactor: Modularize orbitdb access controller.

* chore: Check for correct access controller path and modify if necessary.

* fix: Linting.

* refactor: AC interface no longer needed.

* refactor: Move IPFS-specific AC list back into IPFS AC.

* refactor: Explicitly name access controller param.

* refactor: Pass in manifest settings as object.

* refactor: Config access controllers.

* refactor: ACs should expose specific params before being called with generic params.

* feat: Pass write access to root IPFS AC.

* refactor: AC should handle type prefix.

* test: Test for type.

* refactor: Pass generic access to Database (and inheriting dbs).

* refactor: Use AccessControllers module to manage custom ACs.

* chore: Remove excess console logging.

* test: Fix ipfs module import.
This commit is contained in:
Hayden Young 2023-04-03 19:56:47 +08:00 committed by GitHub
parent a027525e5c
commit 428ce83878
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
20 changed files with 781 additions and 789 deletions

View File

@ -4,7 +4,6 @@ import { Log, Entry } from './oplog/index.js'
import { ComposedStorage, IPFSBlockStorage, LevelStorage, LRUStorage } from './storage/index.js'
import KeyStore from './key-store.js'
import { Identities } from './identities/index.js'
import IPFSAccessController from './access-controllers/ipfs.js'
import OrbitDBAddress, { isValidAddress } from './address.js'
import DBManifest from './manifest.js'
import { createId } from './utils/index.js'
@ -13,6 +12,7 @@ import pathJoin from './utils/path-join.js'
import * as Block from 'multiformats/block'
import * as dagCbor from '@ipld/dag-cbor'
import { sha256 } from 'multiformats/hashes/sha2'
import * as AccessControllers from './access-controllers/index.js'
const codec = dagCbor
const hasher = sha256
@ -26,6 +26,11 @@ const databaseTypes = {
documents: DocumentStore,
keyvalue: KeyValue
}
//
// const accessControllers = {
// ipfs: IPFSAccessController,
// orbitdb: OrbitDBAccessController
// }
const addDatabaseType = (type, store) => {
if (databaseTypes[type]) {
@ -33,6 +38,13 @@ const addDatabaseType = (type, store) => {
}
databaseTypes[type] = store
}
//
// const addAccessController = (type, accessController) => {
// if (accessControllers[type]) {
// throw new Error(`Access Controller already exists: ${type}`)
// }
// accessControllers[type] = accessController
// }
// const defaultTimeout = 30000 // 30 seconds
@ -57,11 +69,11 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
let databases = {}
const open = async (address, { type, meta, write, sync, Store } = {}) => {
const open = async (address, { type, meta, sync, Store, AccessController } = {}) => {
let name, manifest, accessController
if (type && !databaseTypes[type]) {
throw new Error(`Unspported database type: '${type}'`)
throw new Error(`Unsupported database type: '${type}'`)
}
if (databases[address]) {
@ -74,19 +86,23 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
const bytes = await manifestStorage.get(addr.path)
const { value } = await Block.decode({ bytes, codec, hasher })
manifest = value
const acAddress = manifest.accessController.replaceAll('/ipfs/', '')
accessController = await IPFSAccessController({ ipfs, identities, identity, address: acAddress, storage: manifestStorage, write })
const acType = manifest.accessController.split('/', 2).pop()
const acAddress = manifest.accessController.replaceAll(`/${acType}/`, '')
accessController = await AccessControllers.get(acType)()({ orbitdb: { open, identity, ipfs }, identities, address: acAddress })
name = manifest.name
type = type || manifest.type
meta = manifest.meta
} else {
// If the address given was not valid, eg. just the name of the database
type = type || 'events'
accessController = await IPFSAccessController({ ipfs, identities, identity, storage: manifestStorage, write })
const m = await DBManifest(manifestStorage, address, type, accessController.address, { meta })
AccessController = AccessController || AccessControllers.get('ipfs')({ storage: manifestStorage })
accessController = await AccessController({ orbitdb: { open, identity, ipfs }, identities })
const m = await DBManifest({ storage: manifestStorage, name: address, type, accessController: accessController.address, meta })
manifest = m.manifest
address = OrbitDBAddress(m.hash)
accessController = m.accessController
// accessController = manifest.accessController
name = manifest.name
meta = manifest.meta
}
@ -94,10 +110,10 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
const DatabaseModel = Store || databaseTypes[type]
if (!DatabaseModel) {
throw new Error(`Unspported database type: '${type}'`)
throw new Error(`Unsupported database type: '${type}'`)
}
const db = await DatabaseModel({ OpLog, Database, ipfs, identity, address: address.toString(), name, accessController, directory, meta, syncAutomatically: sync != null ? sync : true })
const db = await DatabaseModel({ OpLog, Database, ipfs, identity, address: address.toString(), name, access: accessController, directory, meta, syncAutomatically: sync != null ? sync : true })
db.events.on('close', onDatabaseClosed(address.toString()))
@ -131,7 +147,7 @@ const OrbitDB = async ({ ipfs, id, identity, keystore, directory } = {}) => {
}
}
export { OrbitDB as default, OrbitDBAddress, addDatabaseType, databaseTypes }
export { OrbitDB as default, OrbitDBAddress, addDatabaseType, databaseTypes, AccessControllers }
// class OrbitDB2 {
// constructor (ipfs, identity, options = {}) {

View File

@ -1,70 +1,42 @@
import AccessController from './interface.js'
import AccessControllerManifest from './manifest.js'
// import LegacyIPFSAccessController from './access-controllers/legacy-ipfs.js'
import IPFSAccessController from './ipfs.js'
// import OrbitDBAccessController from './orbitdb.js'
import OrbitDBAccessController from './orbitdb.js'
const supportedTypes = {
// 'legacy-ipfs': LegacyIPFSAccessController,
ipfs: IPFSAccessController
// orbitdb: OrbitDBAccessController
const types = {
ipfs: IPFSAccessController,
orbitdb: OrbitDBAccessController
}
const getHandlerFor = (type) => {
if (!AccessControllers.isSupported(type)) {
const get = (type) => {
if (!isSupported(type)) {
throw new Error(`AccessController type '${type}' is not supported`)
}
return supportedTypes[type]
return types[type]
}
export default class AccessControllers {
static get AccessController () { return AccessController }
static isSupported (type) {
return Object.keys(supportedTypes).includes(type)
}
static addAccessController (options) {
if (!options.AccessController) {
throw new Error('AccessController class needs to be given as an option')
}
if (!options.AccessController.type ||
typeof options.AccessController.type !== 'string') {
throw new Error('Given AccessController class needs to implement: static get type() { /* return a string */}.')
}
supportedTypes[options.AccessController.type] = options.AccessController
}
static addAccessControllers (options) {
const accessControllers = options.AccessControllers
if (!accessControllers) {
throw new Error('AccessController classes need to be given as an option')
}
accessControllers.forEach((accessController) => {
AccessControllers.addAccessController({ AccessController: accessController })
})
}
static removeAccessController (type) {
delete supportedTypes[type]
}
static async resolve (orbitdb, manifestAddress, options = {}) {
const { type, params } = await AccessControllerManifest.resolve(orbitdb._ipfs, manifestAddress, options)
const AccessController = getHandlerFor(type)
const accessController = await AccessController.create(orbitdb, Object.assign({}, options, params))
await accessController.load(params.address)
return accessController
}
static async create ({ ipfs, identity }, type, options = {}) {
const AccessController = getHandlerFor(type)
const ac = await AccessController.create({ ipfs, identity }, options)
const params = await ac.save()
const hash = await AccessControllerManifest.create(ipfs, type, params)
return hash
}
const isSupported = type => {
return Object.keys(types).includes(type)
}
const add = (accessController) => {
if (types[accessController.type]) {
throw new Error(`Access controller '${accessController.type}' already added.`)
}
if (!accessController.type) {
throw new Error('Given AccessController class needs to implement: type.')
}
types[accessController.type] = accessController
}
const remove = type => {
delete types[type]
}
export {
types,
get,
isSupported,
add,
remove
}

View File

@ -1,50 +0,0 @@
import { EventEmitter } from 'events'
/**
* Interface for OrbitDB Access Controllers
*
* Any OrbitDB access controller needs to define and implement
* the methods defined by the interface here.
*/
export default class AccessController extends EventEmitter {
/*
Every AC needs to have a 'Factory' method
that creates an instance of the AccessController
*/
static async create (orbitdb, options) {}
/* Return the type for this controller */
static get type () {
throw new Error('\'static get type ()\' needs to be defined in the inheriting class')
}
/*
Return the type for this controller
NOTE! This is the only property of the interface that
shouldn't be overridden in the inherited Access Controller
*/
get type () {
return this.constructor.type
}
/* Each Access Controller has some address to anchor to */
get address () {}
/*
Called by the databases (the log) to see if entry should
be allowed in the database. Return true if the entry is allowed,
false is not allowed
*/
async canAppend (entry, identityProvider) {}
/* Add and remove access */
async grant (access, identity) { return false }
async revoke (access, identity) { return false }
/* AC creation and loading */
async load (address) {}
/* Returns AC manifest parameters object */
async save () {}
/* Called when the database for this AC gets closed */
async close () {}
}

View File

@ -1,19 +1,15 @@
// import * as io from '../utils/index.js'
// import AccessController from './interface.js'
// import AccessControllerManifest from './manifest.js'
import { IPFSBlockStorage } from '../storage/index.js'
import * as Block from 'multiformats/block'
import * as dagCbor from '@ipld/dag-cbor'
import { sha256 } from 'multiformats/hashes/sha2'
import { base58btc } from 'multiformats/bases/base58'
import pathJoin from '../utils/path-join.js'
const codec = dagCbor
const hasher = sha256
const hashStringEncoding = base58btc
const type = 'ipfs'
const AccessControllerManifest = async ({ storage, type, params }) => {
const AccessControlList = async ({ storage, type, params }) => {
const manifest = {
type,
...params
@ -24,17 +20,19 @@ const AccessControllerManifest = async ({ storage, type, params }) => {
return hash
}
const IPFSAccessController = async ({ ipfs, identities, identity, address, storage, write }) => {
storage = storage || await IPFSBlockStorage({ ipfs, pin: true })
const type = 'ipfs'
write = write || [identity.id]
const IPFSAccessController = ({ write, storage } = {}) => async ({ orbitdb, identities, address }) => {
storage = storage || await IPFSBlockStorage({ ipfs: orbitdb.ipfs, pin: true })
write = write || [orbitdb.identity.id]
if (address) {
const manifestBytes = await storage.get(address)
const manifestBytes = await storage.get(address.replaceAll('/ipfs/', ''))
const { value } = await Block.decode({ bytes: manifestBytes, codec, hasher })
write = value.write
} else {
address = await AccessControllerManifest({ storage, type, params: { write } })
address = await AccessControlList({ storage, type, params: { write } })
address = pathJoin('/', type, address)
}
const canAppend = async (entry) => {
@ -59,4 +57,4 @@ const IPFSAccessController = async ({ ipfs, identities, identity, address, stora
}
}
export { IPFSAccessController as default }
export default IPFSAccessController

View File

@ -1,33 +0,0 @@
// import * as io from 'orbit-db-io'
// export default class AccessControllerManifest {
// constructor (type, params = {}) {
// this.type = type
// this.params = params
// }
// static async resolve (ipfs, manifestHash, options = {}) {
// if (options.skipManifest) {
// if (!options.type) {
// throw new Error('No manifest, access-controller type required')
// }
// return new AccessControllerManifest(options.type, { address: manifestHash })
// } else {
// // TODO: ensure this is a valid multihash
// if (manifestHash.indexOf('/ipfs') === 0) { manifestHash = manifestHash.split('/')[2] }
// const { type, params } = await io.read(ipfs, manifestHash)
// return new AccessControllerManifest(type, params)
// }
// }
// static async create (ipfs, type, params) {
// if (params.skipManifest) {
// return params.address
// }
// const manifest = {
// type,
// params
// }
// return io.write(ipfs, 'dag-cbor', manifest)
// }
// }

View File

@ -1,134 +1,108 @@
import pMapSeries from 'p-map-series'
import AccessController from './interface.js'
import ensureAddress from '../utils/ensure-ac-address.js'
import { EventEmitter } from 'events'
import ensureACAddress from '../utils/ensure-ac-address.js'
import IPFSAccessController from './ipfs.js'
const type = 'orbitdb'
export default class OrbitDBAccessController extends AccessController {
constructor (orbitdb, options) {
super()
this._orbitdb = orbitdb
this._db = null
this._options = options || {}
const OrbitDBAccessController = ({ write } = {}) => async ({ orbitdb, identities, address }) => {
const events = new EventEmitter()
address = address || 'default-access-controller'
write = write || [orbitdb.identity.id]
// Force '<address>/_access' naming for the database
const db = await orbitdb.open(ensureACAddress(address), { type: 'keyvalue', AccessController: IPFSAccessController({ write }) })
address = db.address
const onUpdate = (entry) => {
events.emit('update', entry)
}
// Returns the type of the access controller
static get type () { return type }
// Returns the address of the OrbitDB used as the AC
get address () {
return this._db.address
}
db.events.on('update', onUpdate)
// Return true if entry is allowed to be added to the database
async canAppend (entry, identityProvider) {
// Write keys and admins keys are allowed
const access = new Set([...this.get('write'), ...this.get('admin')])
const canAppend = async (entry) => {
const writerIdentity = await identities.getIdentity(entry.identity)
if (!writerIdentity) {
return false
}
const { id } = writerIdentity
// If the ACL contains the writer's public key or it contains '*'
if (access.has(entry.identity.id) || access.has('*')) {
const verifiedIdentity = await identityProvider.verifyIdentity(entry.identity)
// Allow access if identity verifies
return verifiedIdentity
const hasWriteAccess = await hasCapability('write', id) || await hasCapability('admin', id)
if (hasWriteAccess) {
return identities.verifyIdentity(writerIdentity)
}
return false
}
get capabilities () {
if (this._db) {
const capabilities = this._db.index
const toSet = (e) => {
const key = e[0]
capabilities[key] = new Set([...(capabilities[key] || []), ...e[1]])
}
// Merge with the access controller of the database
// and make sure all values are Sets
Object.entries({
...capabilities,
// Add the root access controller's 'write' access list
// as admins on this controller
...{ admin: new Set([...(capabilities.admin || []), ...this._db.access.write]) }
}).forEach(toSet)
return capabilities
const capabilities = async () => {
const _capabilities = []
for await (const entry of db.iterator()) {
_capabilities[entry.key] = entry.value
}
return {}
}
get (capability) {
return this.capabilities[capability] || new Set([])
}
async close () {
await this._db.close()
}
async load (address) {
if (this._db) { await this._db.close() }
// Force '<address>/_access' naming for the database
this._db = await this._orbitdb.keyvalue(ensureAddress(address), {
// use ipfs controller as a immutable "root controller"
accessController: {
type: 'ipfs',
write: this._options.admin || [this._orbitdb.identity.id]
},
sync: true
})
this._db.events.on('ready', this._onUpdate.bind(this))
this._db.events.on('write', this._onUpdate.bind(this))
this._db.events.on('replicated', this._onUpdate.bind(this))
await this._db.load()
}
async save () {
// return the manifest data
return {
address: this._db.address.toString()
const toSet = (e) => {
const key = e[0]
_capabilities[key] = new Set([...(_capabilities[key] || []), ...e[1]])
}
// Merge with the access controller of the database
// and make sure all values are Sets
Object.entries({
..._capabilities,
// Add the root access controller's 'write' access list
// as admins on this controller
...{ admin: new Set([...(_capabilities.admin || []), ...write]) }
}).forEach(toSet)
return _capabilities
}
async hasCapability (capability, identity) {
const get = async (capability) => {
const _capabilities = await capabilities()
return _capabilities[capability] || new Set([])
}
const close = async () => {
await db.close()
}
const hasCapability = async (capability, key) => {
// Write keys and admins keys are allowed
const access = new Set(this.get(capability))
return access.has(identity.id) || access.has('*')
const access = new Set(await get(capability))
return access.has(key) || access.has('*')
}
async grant (capability, key) {
const grant = async (capability, key) => {
// Merge current keys with the new key
const capabilities = new Set([...(this._db.get(capability) || []), ...[key]])
await this._db.put(capability, Array.from(capabilities.values()))
const capabilities = new Set([...(await db.get(capability) || []), ...[key]])
await db.put(capability, Array.from(capabilities.values()))
}
async revoke (capability, key) {
const capabilities = new Set(this._db.get(capability) || [])
const revoke = async (capability, key) => {
const capabilities = new Set(await db.get(capability) || [])
capabilities.delete(key)
if (capabilities.size > 0) {
await this._db.put(capability, Array.from(capabilities.values()))
await db.put(capability, Array.from(capabilities.values()))
} else {
await this._db.del(capability)
await db.del(capability)
}
}
/* Private methods */
_onUpdate () {
this.emit('updated')
}
/* Factory */
static async create (orbitdb, options = {}) {
const ac = new OrbitDBAccessController(orbitdb, options)
await ac.load(options.address || options.name || 'default-access-controller')
// Add write access from options
if (options.write && !options.address) {
await pMapSeries(options.write, async (e) => ac.grant('write', e))
}
return ac
return {
type,
address,
write,
canAppend,
capabilities,
get,
grant,
revoke,
close,
events
}
}
export default OrbitDBAccessController

View File

@ -7,7 +7,7 @@ import pathJoin from './utils/path-join.js'
const defaultReferencesCount = 16
const defaultCacheSize = 1000
const Database = async ({ OpLog, ipfs, identity, address, name, accessController, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically }) => {
const Database = async ({ OpLog, ipfs, identity, address, name, access, directory, meta, headsStorage, entryStorage, indexStorage, referencesCount, syncAutomatically }) => {
const { Log, Entry } = OpLog
directory = pathJoin(directory || './orbitdb', `./${address}/`)
@ -29,7 +29,7 @@ const Database = async ({ OpLog, ipfs, identity, address, name, accessController
await LevelStorage({ path: pathJoin(directory, '/log/_index/') })
)
const log = await Log(identity, { logId: address, access: accessController, entryStorage, headsStorage, indexStorage })
const log = await Log(identity, { logId: address, access, entryStorage, headsStorage, indexStorage })
const events = new EventEmitter()
const queue = new PQueue({ concurrency: 1 })
@ -88,7 +88,8 @@ const Database = async ({ OpLog, ipfs, identity, address, name, accessController
log,
sync,
peers: sync.peers,
events
events,
access
}
}

View File

@ -1,5 +1,5 @@
const DocumentStore = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically, indexBy = '_id' }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically })
const DocumentStore = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically, indexBy = '_id' }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
const { addOperation, log } = database

View File

@ -1,5 +1,5 @@
const Events = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically })
const Events = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
const { addOperation, log } = database

View File

@ -5,8 +5,8 @@ import PQueue from 'p-queue'
const valueEncoding = 'json'
const KeyValuePersisted = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta }) => {
const keyValueStore = await KeyValue({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta })
const KeyValuePersisted = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta }) => {
const keyValueStore = await KeyValue({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta })
const { events, log } = keyValueStore
const queue = new PQueue({ concurrency: 1 })

View File

@ -1,5 +1,5 @@
const KeyValue = async ({ OpLog, Database, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, accessController, directory, storage, meta, syncAutomatically })
const KeyValue = async ({ OpLog, Database, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically }) => {
const database = await Database({ OpLog, ipfs, identity, address, name, access, directory, storage, meta, syncAutomatically })
const { addOperation, log } = database

View File

@ -1,4 +1,3 @@
import pathJoin from './utils/path-join.js'
import * as Block from 'multiformats/block'
import * as dagCbor from '@ipld/dag-cbor'
import { sha256 } from 'multiformats/hashes/sha2'
@ -9,17 +8,17 @@ const hasher = sha256
const hashStringEncoding = base58btc
// Creates a DB manifest file and saves it in IPFS
export default async (storage, name, type, accessControllerAddress, { meta } = {}) => {
export default async ({ storage, name, type, accessController, meta }) => {
if (!storage) throw new Error('storage is required')
if (!name) throw new Error('name is required')
if (!type) throw new Error('type is required')
if (!accessControllerAddress) throw new Error('accessControllerAddress is required')
if (!accessController) throw new Error('accessController is required')
const manifest = Object.assign(
{
name,
type,
accessController: pathJoin('/ipfs', accessControllerAddress)
accessController
},
// meta field is only added to manifest if meta parameter is defined
meta !== undefined ? { meta } : {}

View File

@ -0,0 +1,9 @@
import path from 'path'
// Make sure the given address has '/_access' as the last part
export default address => {
const suffix = address.toString().split('/').pop()
return suffix === '_access'
? address
: path.join(address, '/_access')
}

View File

@ -1,143 +1,138 @@
// import assert from 'assert'
// import rmrf from 'rimraf'
// import Web3 from 'web3'
// // import Web3 from 'web3'
// import OrbitDB from '../../src/OrbitDB.js'
// import IdentityProvider from 'orbit-db-identity-provider'
// import Keystore from 'orbit-db-keystore'
// import AccessControllers from 'orbit-db-access-controllers'
// import ContractAccessController from 'orbit-db-access-controllers/contract'
// import ganache from 'ganache-cli'
// import Access from './Access.json' assert {type: "json"}
// // Include test utilities
// import {
// config,
// startIpfs,
// stopIpfs,
// testAPIs
// } from 'orbit-db-test-utils'
//
// // import IdentityProvider from 'orbit-db-identity-provider'
// import Keystore from '../../src/key-store.js'
// import AccessControllers from '../../src/access-controllers/index.js'
// // import ContractAccessController from 'orbit-db-access-controllers/contract'
// // import ganache from 'ganache-cli'
// // import Access from './Access.json' assert {type: "json"}
// import config from '../config.js'
// import connectPeers from '../utils/connect-nodes.js'
//
// const abi = Access.abi
// const bytecode = Access.bytecode
// const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
// const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
// Object.keys(testAPIs).forEach(API => {
// describe(`orbit-db - Access Controller Handlers (${API})`, function () {
// this.timeout(config.timeout)
// let web3, contract, ipfsd1, ipfsd2, ipfs1, ipfs2, id1, id2
// let orbitdb1, orbitdb2
// before(async () => {
// rmrf.sync(dbPath1)
// rmrf.sync(dbPath2)
// ipfsd1 = await startIpfs(API, config.daemon1)
// ipfsd2 = await startIpfs(API, config.daemon2)
// ipfs1 = ipfsd1.api
// ipfs2 = ipfsd2.api
// const keystore1 = new Keystore(dbPath1 + '/keys')
// const keystore2 = new Keystore(dbPath2 + '/keys')
// id1 = await IdentityProvider.createIdentity({ id: 'A', keystore: keystore1 })
// id2 = await IdentityProvider.createIdentity({ id: 'B', keystore: keystore2 })
// orbitdb1 = await OrbitDB.createInstance(ipfs1, {
// AccessControllers: AccessControllers,
// directory: dbPath1,
// identity: id1
// })
// orbitdb2 = await OrbitDB.createInstance(ipfs2, {
// AccessControllers: AccessControllers,
// directory: dbPath2,
// identity: id2
// })
//
// describe('Access Controller Handlers', function () {
// this.timeout(config.timeout)
//
// let ipfs1, ipfs2
// let orbitdb1, orbitdb2
//
// before(async () => {
// ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
// ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
// await connectPeers(ipfs1, ipfs2)
//
// const keystore1 = await Keystore({ path: dbPath1 + '/keys' })
// const keystore2 = await Keystore({ path: dbPath2 + '/keys' })
//
// identities1 = await Identities({ keystore: keystore1 })
// identities2 = await Identities({ keystore: keystore2 })
//
// testIdentity1 = await identities1.createIdentity({ id: 'userA' })
// testIdentity2 = await identities2.createIdentity({ id: 'userB' })
//
// orbitdb1 = await OrbitDB({ ipfs: ipfs1, identity: testIdentity1, directory: dbPath1 })
// orbitdb2 = await OrbitDB({ ipfs: ipfs2, identity: testIdentity2, directory: dbPath2 })
// })
//
// after(async () => {
// if (orbitdb1) {
// await orbitdb1.stop()
// }
//
// if (orbitdb2) {
// await orbitdb2.stop()
// }
//
// if (ipfs1) {
// await ipfs1.stop()
// }
//
// if (ipfs2) {
// await ipfs2.stop()
// }
//
// await rmrf('./orbitdb')
// await rmrf('./ipfs1')
// await rmrf('./ipfs2')
// })
//
// describe.only('isSupported', function () {
// it('supports default access controllers', () => {
// assert.strictEqual(AccessControllers.isSupported('ipfs'), true)
// assert.strictEqual(AccessControllers.isSupported('orbitdb'), true)
// })
// after(async () => {
// if (orbitdb1) { await orbitdb1.stop() }
// if (orbitdb2) { await orbitdb2.stop() }
// if (ipfsd1) { await stopIpfs(ipfsd1) }
// if (ipfsd2) { await stopIpfs(ipfsd2) }
// })
// describe('isSupported', function () {
// it('supports default access controllers', () => {
// assert.strictEqual(AccessControllers.isSupported('ipfs'), true)
// assert.strictEqual(AccessControllers.isSupported('orbitdb'), true)
// })
// it('doesn\'t support smart contract access controller by default', () => {
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
// })
// })
// describe('addAccessController', function () {
// it('supports added access controller', () => {
// const options = {
// AccessController: ContractAccessController,
// web3: web3,
// abi: abi
// }
// AccessControllers.addAccessController(options)
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), true)
// })
// })
// describe('create access controllers', function () {
// let options = {
// AccessController: ContractAccessController
// }
// before(async () => {
// web3 = new Web3(ganache.provider())
// const accounts = await web3.eth.getAccounts()
// contract = await new web3.eth.Contract(abi)
// .deploy({ data: bytecode })
// .send({ from: accounts[0], gas: '1000000' })
// options = Object.assign({}, options, { web3, abi, contractAddress: contract._address, defaultAccount: accounts[0] })
// AccessControllers.addAccessController(options)
// })
// it('throws an error if AccessController is not defined', async () => {
// let err
// try {
// AccessControllers.addAccessController({})
// } catch (e) {
// err = e.toString()
// }
// assert.strictEqual(err, 'Error: AccessController class needs to be given as an option')
// })
// it('throws an error if AccessController doesn\'t define type', async () => {
// let err
// try {
// AccessControllers.addAccessController({ AccessController: {} })
// } catch (e) {
// err = e.toString()
// }
// assert.strictEqual(err, 'Error: Given AccessController class needs to implement: static get type() { /* return a string */}.')
// })
// it('creates a custom access controller', async () => {
// const type = ContractAccessController.type
// const acManifestHash = await AccessControllers.create(orbitdb1, type, options)
// assert.notStrictEqual(acManifestHash, null)
// const ac = await AccessControllers.resolve(orbitdb1, acManifestHash, options)
// assert.strictEqual(ac.type, type)
// })
// it('removes the custom access controller', async () => {
// AccessControllers.removeAccessController(ContractAccessController.type)
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
// })
//
// it('doesn\'t support smart contract access controller by default', () => {
// assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
// })
// })
//
// // describe('addAccessController', function () {
// // it('supports added access controller', () => {
// // const options = {
// // AccessController: ContractAccessController,
// // web3: web3,
// // abi: abi
// // }
// // AccessControllers.addAccessController(options)
// // assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), true)
// // })
// // })
// //
// // describe('create access controllers', function () {
// // let options = {
// // AccessController: ContractAccessController
// // }
// //
// // before(async () => {
// // web3 = new Web3(ganache.provider())
// // const accounts = await web3.eth.getAccounts()
// // contract = await new web3.eth.Contract(abi)
// // .deploy({ data: bytecode })
// // .send({ from: accounts[0], gas: '1000000' })
// // options = Object.assign({}, options, { web3, abi, contractAddress: contract._address, defaultAccount: accounts[0] })
// // AccessControllers.addAccessController(options)
// // })
// //
// // it('throws an error if AccessController is not defined', async () => {
// // let err
// // try {
// // AccessControllers.addAccessController({})
// // } catch (e) {
// // err = e.toString()
// // }
// // assert.strictEqual(err, 'Error: AccessController class needs to be given as an option')
// // })
// //
// // it('throws an error if AccessController doesn\'t define type', async () => {
// // let err
// // try {
// // AccessControllers.addAccessController({ AccessController: {} })
// // } catch (e) {
// // err = e.toString()
// // }
// // assert.strictEqual(err, 'Error: Given AccessController class needs to implement: static get type() { /* return a string */}.')
// // })
// //
// // it('creates a custom access controller', async () => {
// // const type = ContractAccessController.type
// // const acManifestHash = await AccessControllers.create(orbitdb1, type, options)
// // assert.notStrictEqual(acManifestHash, null)
// //
// // const ac = await AccessControllers.resolve(orbitdb1, acManifestHash, options)
// // assert.strictEqual(ac.type, type)
// // })
// //
// // it('removes the custom access controller', async () => {
// // AccessControllers.removeAccessController(ContractAccessController.type)
// // assert.strictEqual(AccessControllers.isSupported(ContractAccessController.type), false)
// // })
// // })
// })

View File

@ -17,6 +17,7 @@ describe('IPFSAccessController', function () {
let keystore1, keystore2
let identities1, identities2
let testIdentity1, testIdentity2
let orbitdb1, orbitdb2
before(async () => {
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
@ -31,6 +32,9 @@ describe('IPFSAccessController', function () {
testIdentity1 = await identities1.createIdentity({ id: 'userA' })
testIdentity2 = await identities2.createIdentity({ id: 'userB' })
orbitdb1 = { ipfs: ipfs1, identity: testIdentity1 }
orbitdb2 = { ipfs: ipfs2, identity: testIdentity2 }
})
after(async () => {
@ -57,59 +61,72 @@ describe('IPFSAccessController', function () {
let accessController
before(async () => {
accessController = await IPFSAccessController({
ipfs: ipfs1,
identities: identities1,
identity: testIdentity1
describe('Default write access', () => {
before(async () => {
accessController = await IPFSAccessController()({
orbitdb: orbitdb1,
identities: identities1
})
})
it('creates an access controller', () => {
notStrictEqual(accessController, null)
notStrictEqual(accessController, undefined)
})
it('sets the controller type', () => {
strictEqual(accessController.type, 'ipfs')
})
it('sets default write', async () => {
deepStrictEqual(accessController.write, [testIdentity1.id])
})
it('user with write access can append', async () => {
const mockEntry = {
identity: testIdentity1.hash,
v: 1
// ...
// doesn't matter what we put here, only identity is used for the check
}
const canAppend = await accessController.canAppend(mockEntry)
strictEqual(canAppend, true)
})
it('user without write cannot append', async () => {
const mockEntry = {
identity: testIdentity2.hash,
v: 1
// ...
// doesn't matter what we put here, only identity is used for the check
}
const canAppend = await accessController.canAppend(mockEntry)
strictEqual(canAppend, false)
})
it('replicates the access controller', async () => {
const replicatedAccessController = await IPFSAccessController()({
orbitdb: orbitdb2,
identities: identities2,
address: accessController.address
})
strictEqual(replicatedAccessController.type, accessController.type)
strictEqual(replicatedAccessController.address, accessController.address)
deepStrictEqual(replicatedAccessController.write, accessController.write)
})
})
it('creates an access controller', () => {
notStrictEqual(accessController, null)
notStrictEqual(accessController, undefined)
})
it('sets the controller type', () => {
strictEqual(accessController.type, 'ipfs')
})
it('sets default write', async () => {
deepStrictEqual(accessController.write, [testIdentity1.id])
})
it('user with write access can append', async () => {
const mockEntry = {
identity: testIdentity1.hash,
v: 1
// ...
// doesn't matter what we put here, only identity is used for the check
}
const canAppend = await accessController.canAppend(mockEntry)
strictEqual(canAppend, true)
})
it('user without write cannot append', async () => {
const mockEntry = {
identity: testIdentity2.hash,
v: 1
// ...
// doesn't matter what we put here, only identity is used for the check
}
const canAppend = await accessController.canAppend(mockEntry)
strictEqual(canAppend, false)
})
it('replicates the access controller', async () => {
const replicatedAccessController = await IPFSAccessController({
ipfs: ipfs2,
identities: identities2,
identity: testIdentity2,
address: accessController.address
describe('Write all access', () => {
before(async () => {
accessController = await IPFSAccessController({ write: ['*'] })({
orbitdb: orbitdb1,
identities: identities1
})
})
strictEqual(replicatedAccessController.type, accessController.type)
strictEqual(replicatedAccessController.address, accessController.address)
deepStrictEqual(replicatedAccessController.write, accessController.write)
it('sets write to \'Anyone\'', async () => {
deepStrictEqual(accessController.write, ['*'])
})
})
})

View File

@ -1,336 +1,277 @@
// import assert from 'assert'
// import rmrf from 'rimraf'
// import OrbitDB from '../../src/OrbitDB.js'
// import IdentityProvider from 'orbit-db-identity-provider'
// import Keystore from 'orbit-db-keystore'
// import OrbitDBAccessController from 'orbit-db-access-controllers/orbitdb'
// import AccessControllers from 'orbit-db-access-controllers'
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
import rmrf from 'rimraf'
import OrbitDB from '../../src/OrbitDB.js'
import * as IPFS from 'ipfs-core'
import Keystore from '../../src/key-store.js'
import Identities from '../../src/identities/identities.js'
import OrbitDBAccessController from '../../src/access-controllers/orbitdb.js'
import config from '../config.js'
import connectPeers from '../utils/connect-nodes.js'
// // Include test utilities
// import {
// config,
// startIpfs,
// stopIpfs,
// testAPIs
// } from 'orbit-db-test-utils'
const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
// const dbPath1 = './orbitdb/tests/orbitdb-access-controller/1'
// const dbPath2 = './orbitdb/tests/orbitdb-access-controller/2'
describe('OrbitDBAccessController', function () {
this.timeout(config.timeout)
// Object.keys(testAPIs).forEach(API => {
// describe(`orbit-db - OrbitDBAccessController (${API})`, function () {
// this.timeout(config.timeout)
let ipfs1, ipfs2
let orbitdb1, orbitdb2
let identities1, identities2, testIdentity1, testIdentity2
// let ipfsd1, ipfsd2, ipfs1, ipfs2, id1, id2
// let orbitdb1, orbitdb2
before(async () => {
ipfs1 = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
ipfs2 = await IPFS.create({ ...config.daemon2, repo: './ipfs2' })
await connectPeers(ipfs1, ipfs2)
// before(async () => {
// rmrf.sync(dbPath1)
// rmrf.sync(dbPath2)
// ipfsd1 = await startIpfs(API, config.daemon1)
// ipfsd2 = await startIpfs(API, config.daemon2)
// ipfs1 = ipfsd1.api
// ipfs2 = ipfsd2.api
const keystore1 = await Keystore({ path: dbPath1 + '/keys' })
const keystore2 = await Keystore({ path: dbPath2 + '/keys' })
// const keystore1 = new Keystore(dbPath1 + '/keys')
// const keystore2 = new Keystore(dbPath2 + '/keys')
identities1 = await Identities({ ipfs: ipfs1, keystore: keystore1 })
identities2 = await Identities({ ipfs: ipfs2, keystore: keystore2 })
// id1 = await IdentityProvider.createIdentity({ id: 'A', keystore: keystore1 })
// id2 = await IdentityProvider.createIdentity({ id: 'B', keystore: keystore2 })
testIdentity1 = await identities1.createIdentity({ id: 'userA' })
testIdentity2 = await identities2.createIdentity({ id: 'userB' })
// orbitdb1 = await OrbitDB.createInstance(ipfs1, {
// AccessControllers,
// directory: dbPath1,
// identity: id1
// })
orbitdb1 = await OrbitDB({ ipfs: ipfs1, identity: testIdentity1, directory: dbPath1, keystore: keystore1 })
orbitdb2 = await OrbitDB({ ipfs: ipfs2, identity: testIdentity2, directory: dbPath2, keystore: keystore2 })
})
// orbitdb2 = await OrbitDB.createInstance(ipfs2, {
// AccessControllers,
// directory: dbPath2,
// identity: id2
// })
// })
after(async () => {
if (orbitdb1) {
await orbitdb1.stop()
}
// after(async () => {
// if (orbitdb1) {
// await orbitdb1.stop()
// }
if (orbitdb2) {
await orbitdb2.stop()
}
// if (orbitdb2) {
// await orbitdb2.stop()
// }
if (ipfs1) {
await ipfs1.stop()
}
// if (ipfsd1) {
// await stopIpfs(ipfsd1)
// }
if (ipfs2) {
await ipfs2.stop()
}
// if (ipfsd2) {
// await stopIpfs(ipfsd2)
// }
// })
await rmrf('./orbitdb')
await rmrf('./ipfs1')
await rmrf('./ipfs2')
})
// describe('Constructor', function () {
// let accessController
describe('Default write access', function () {
let accessController
// before(async () => {
// accessController = await OrbitDBAccessController.create(orbitdb1)
// })
before(async () => {
accessController = await OrbitDBAccessController()({ orbitdb: orbitdb1, identities: identities1 })
})
// it('creates an access controller', () => {
// assert.notStrictEqual(accessController, null)
// assert.notStrictEqual(accessController, undefined)
// })
it('creates an access controller', () => {
notStrictEqual(accessController, null)
notStrictEqual(accessController, undefined)
})
// it('sets the controller type', () => {
// assert.strictEqual(accessController.type, 'orbitdb')
// })
it('sets the controller type', () => {
strictEqual(accessController.type, 'orbitdb')
})
// it('has OrbitDB instance', async () => {
// assert.notStrictEqual(accessController._orbitdb, null)
// assert.strictEqual(accessController._orbitdb.id, orbitdb1.id)
// })
it('sets default capabilities', async () => {
const expected = []
expected.admin = new Set([testIdentity1.id])
// it('has IPFS instance', async () => {
// const peerId1 = await accessController._orbitdb._ipfs.id()
// const peerId2 = await ipfs1.id()
// assert.strictEqual(String(peerId1.id), String(peerId2.id))
// })
deepStrictEqual(await accessController.capabilities(), expected)
})
// it('sets default capabilities', async () => {
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id])
// })
// })
it('allows owner to append after creation', async () => {
const mockEntry = {
identity: testIdentity1.hash
// ...
// doesn't matter what we put here, only identity is used for the check
}
const canAppend = await accessController.canAppend(mockEntry)
strictEqual(canAppend, true)
})
})
// it('allows owner to append after creation', async () => {
// const mockEntry = {
// identity: id1
// // ...
// // doesn't matter what we put here, only identity is used for the check
// }
// const canAppend = await accessController.canAppend(mockEntry, id1.provider)
// assert.strictEqual(canAppend, true)
// })
// })
describe('grant', function () {
let accessController
// describe('grant', function () {
// let accessController
before(async () => {
accessController = await OrbitDBAccessController()({ orbitdb: orbitdb1, identities: identities1, address: 'testdb/add' })
})
// before(async () => {
// accessController = new OrbitDBAccessController(orbitdb1)
// await accessController.load('testdb/add')
// })
it('adds a capability', async () => {
try {
await accessController.grant('write', testIdentity1.id)
} catch (e) {
strictEqual(e, null)
}
// it('loads the root access controller from IPFS', () => {
// assert.strictEqual(accessController._db.access.type, 'ipfs')
// assert.deepStrictEqual(accessController._db.access.write, [id1.id])
// })
const expected = []
expected.admin = new Set([testIdentity1.id])
expected.write = new Set([testIdentity1.id])
deepStrictEqual(await accessController.capabilities(), expected)
})
// it('adds a capability', async () => {
// try {
// await accessController.grant('write', id1.id)
// } catch (e) {
// assert(e, null)
// }
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id]),
// write: new Set([id1.id])
// })
// })
it('adds more capabilities', async () => {
try {
await accessController.grant('read', 'ABCD')
await accessController.grant('delete', 'ABCD')
} catch (e) {
strictEqual(e, null)
}
// it('adds more capabilities', async () => {
// try {
// await accessController.grant('read', 'ABCD')
// await accessController.grant('delete', 'ABCD')
// } catch (e) {
// assert.strictEqual(e, null)
// }
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id]),
// write: new Set([id1.id]),
// read: new Set(['ABCD']),
// delete: new Set(['ABCD'])
// })
// })
const expected = []
expected.admin = new Set([testIdentity1.id])
expected.write = new Set([testIdentity1.id])
expected.read = new Set(['ABCD'])
expected.delete = new Set(['ABCD'])
// it('emit \'updated\' event when a capability was added', async () => {
// return new Promise((resolve, reject) => {
// accessController.on('updated', () => {
// try {
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id]),
// write: new Set([id1.id]),
// read: new Set(['ABCD', 'AXES']),
// delete: new Set(['ABCD'])
// })
// resolve()
// } catch (e) {
// reject(e)
// }
// })
// accessController.grant('read', 'AXES')
// })
// })
deepStrictEqual(await accessController.capabilities(), expected)
})
// it('can append after acquiring capability', async () => {
// try {
// await accessController.grant('write', id1.id)
// await accessController.grant('write', id2.id)
// } catch (e) {
// assert(e, null)
// }
// const mockEntry1 = {
// identity: id1
// }
// const mockEntry2 = {
// identity: id2
// }
// const canAppend1 = await accessController.canAppend(mockEntry1, id1.provider)
// const canAppend2 = await accessController.canAppend(mockEntry2, id2.provider)
// assert.strictEqual(canAppend1, true)
// assert.strictEqual(canAppend2, true)
// })
// })
it('emit \'update\' event when a capability was added', async () => {
let update = false
const onUpdate = (entry) => {
update = true
}
// describe('revoke', function () {
// let accessController
accessController.events.on('update', onUpdate)
// before(async () => {
// accessController = new OrbitDBAccessController(orbitdb1)
// await accessController.load('testdb/remove')
// })
await accessController.grant('read', 'AXES')
// it('removes a capability', async () => {
// try {
// await accessController.grant('write', id1.id)
// await accessController.grant('write', 'AABB')
// await accessController.revoke('write', 'AABB')
// } catch (e) {
// assert.strictEqual(e, null)
// }
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id]),
// write: new Set([id1.id])
// })
// })
strictEqual(update, true)
})
// it('can remove the creator\'s write access', async () => {
// try {
// await accessController.revoke('write', id1.id)
// } catch (e) {
// assert.strictEqual(e, null)
// }
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id])
// })
// })
it('can append after acquiring capability', async () => {
try {
await accessController.grant('write', testIdentity1.id)
await accessController.grant('write', testIdentity2.id)
} catch (e) {
strictEqual(e, null)
}
// it('can\'t remove the creator\'s admin access', async () => {
// try {
// await accessController.revoke('admin', id1.id)
// } catch (e) {
// assert.strictEqual(e, null)
// }
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id])
// })
// })
const mockEntry1 = {
identity: testIdentity1.hash
}
// it('removes more capabilities', async () => {
// try {
// await accessController.grant('read', 'ABCD')
// await accessController.grant('delete', 'ABCD')
// await accessController.grant('write', id1.id)
// await accessController.revoke('read', 'ABCDE')
// await accessController.revoke('delete', 'ABCDE')
// } catch (e) {
// assert.strictEqual(e, null)
// }
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id]),
// delete: new Set(['ABCD']),
// read: new Set(['ABCD']),
// write: new Set([id1.id])
// })
// })
const mockEntry2 = {
identity: testIdentity2.hash
}
// it('can\'t append after revoking capability', async () => {
// try {
// await accessController.grant('write', id2.id)
// await accessController.revoke('write', id2.id)
// } catch (e) {
// assert(e, null)
// }
// const mockEntry1 = {
// identity: id1
// }
// const mockEntry2 = {
// identity: id2
// }
// const canAppend = await accessController.canAppend(mockEntry1, id1.provider)
// const noAppend = await accessController.canAppend(mockEntry2, id2.provider)
// assert.strictEqual(canAppend, true)
// assert.strictEqual(noAppend, false)
// })
const canAppend1 = await accessController.canAppend(mockEntry1)
// it('emits \'updated\' event when a capability was removed', async () => {
// await accessController.grant('admin', 'cats')
// await accessController.grant('admin', 'dogs')
const accessController2 = await OrbitDBAccessController()({ orbitdb: orbitdb2, identities: identities2, address: 'testdb/add' })
const canAppend2 = await accessController2.canAppend(mockEntry2)
// return new Promise((resolve, reject) => {
// accessController.on('updated', () => {
// try {
// assert.deepStrictEqual(accessController.capabilities, {
// admin: new Set([id1.id, 'dogs']),
// delete: new Set(['ABCD']),
// read: new Set(['ABCD']),
// write: new Set([id1.id])
// })
// resolve()
// } catch (e) {
// reject(e)
// }
// })
// accessController.revoke('admin', 'cats')
// })
// })
// })
strictEqual(canAppend1, true)
strictEqual(canAppend2, true)
})
})
// describe('save and load', function () {
// let accessController, dbName
describe('revoke', function () {
let accessController
// before(async () => {
// dbName = 'testdb-load-' + new Date().getTime()
// accessController = new OrbitDBAccessController(orbitdb1)
// await accessController.load(dbName)
// await accessController.grant('write', 'A')
// await accessController.grant('write', 'B')
// await accessController.grant('write', 'C')
// await accessController.grant('write', 'C') // double entry
// await accessController.grant('another', 'AA')
// await accessController.grant('another', 'BB')
// await accessController.revoke('another', 'AA')
// await accessController.grant('admin', id1.id)
// return new Promise((resolve) => {
// // Test that the access controller emits 'updated' after it was loaded
// accessController.on('updated', () => resolve())
// accessController.load(accessController.address)
// })
// })
before(async () => {
accessController = await OrbitDBAccessController()({ orbitdb: orbitdb1, identities: identities1, address: 'testdb/remove' })
})
// it('has the correct database address for the internal db', async () => {
// const addr = accessController._db.address.toString().split('/')
// assert.strictEqual(addr[addr.length - 1], '_access')
// assert.strictEqual(addr[addr.length - 2], dbName)
// })
it('removes a capability', async () => {
try {
await accessController.grant('write', testIdentity1.id)
await accessController.grant('write', 'AABB')
await accessController.revoke('write', 'AABB')
} catch (e) {
strictEqual(e, null)
}
// it('has correct capabalities', async () => {
// assert.deepStrictEqual(accessController.get('admin'), new Set([id1.id]))
// assert.deepStrictEqual(accessController.get('write'), new Set(['A', 'B', 'C']))
// assert.deepStrictEqual(accessController.get('another'), new Set(['BB']))
// })
// })
// })
// // TODO: use two separate peers for testing the AC
// // TODO: add tests for revocation correctness with a database (integration tests)
// })
const expected = []
expected.admin = new Set([testIdentity1.id])
expected.write = new Set([testIdentity1.id])
deepStrictEqual(await accessController.capabilities(), expected)
})
it('can remove the creator\'s write access', async () => {
try {
await accessController.revoke('write', testIdentity1.id)
} catch (e) {
strictEqual(e, null)
}
const expected = []
expected.admin = new Set([testIdentity1.id])
deepStrictEqual(await accessController.capabilities(), expected)
})
it('can\'t remove the creator\'s admin access', async () => {
try {
await accessController.revoke('admin', testIdentity1.id)
} catch (e) {
strictEqual(e, null)
}
const expected = []
expected.admin = new Set([testIdentity1.id])
deepStrictEqual(await accessController.capabilities(), expected)
})
it('removes more capabilities', async () => {
try {
await accessController.grant('read', 'ABCD')
await accessController.grant('delete', 'ABCD')
await accessController.grant('write', testIdentity1.id)
await accessController.revoke('read', 'ABCDE')
await accessController.revoke('delete', 'ABCDE')
} catch (e) {
strictEqual(e, null)
}
const expected = []
expected.admin = new Set([testIdentity1.id])
expected.write = new Set([testIdentity1.id])
expected.read = new Set(['ABCD'])
expected.delete = new Set(['ABCD'])
deepStrictEqual(await accessController.capabilities(), expected)
})
it('can\'t append after revoking capability', async () => {
try {
await accessController.grant('write', testIdentity2.id)
await accessController.revoke('write', testIdentity2.id)
} catch (e) {
strictEqual(e, null)
}
const mockEntry1 = {
identity: testIdentity1.hash
}
const mockEntry2 = {
identity: testIdentity2.hash
}
const canAppend = await accessController.canAppend(mockEntry1)
const noAppend = await accessController.canAppend(mockEntry2)
strictEqual(canAppend, true)
strictEqual(noAppend, false)
})
it('emits \'update\' event when a capability was removed', async () => {
await accessController.grant('admin', 'cats')
await accessController.grant('admin', 'dogs')
let update = false
const onUpdate = (entry) => {
update = true
}
accessController.events.on('update', onUpdate)
await accessController.revoke('admin', 'cats')
strictEqual(update, true)
})
})
})
// TODO: use two separate peers for testing the AC
// TODO: add tests for revocation correctness with a database (integration tests)

View File

@ -1,5 +1,4 @@
import { strictEqual, deepStrictEqual } from 'assert'
import path from 'path'
import rmrf from 'rimraf'
import * as IPFS from 'ipfs-core'
import Manifest from '../src/manifest.js'
@ -24,16 +23,16 @@ describe('Manifest', () => {
it('creates a manifest', async () => {
const name = 'manifest'
const type = 'manifest-test'
const accessController = '123'
const expectedHash = 'zdpuAtUvd7EhN9Xu2KSCxkjG1oS1SN6EnnZ8sxvJMPiJhbQWF'
const type = 'manifest'
const accessController = 'test/default-access-controller'
const expectedHash = 'zdpuAx3LaygjPHa2zsUmRoR4jQPm2WYrExsvz2gncfm62aRKv'
const expectedManifest = {
name,
type,
accessController: path.join('/ipfs', accessController)
accessController
}
const { hash, manifest } = await Manifest(storage, name, type, accessController)
const { hash, manifest } = await Manifest({ storage, name, type, accessController })
strictEqual(hash, expectedHash)
deepStrictEqual(manifest, expectedManifest)
@ -41,12 +40,12 @@ describe('Manifest', () => {
it('creates a manifest with metadata', async () => {
const name = 'manifest'
const type = 'manifest-test'
const accessController = '123'
const expectedHash = 'zdpuAmNAMNnzKJ2kWgo4H42ZDG7nFCSGEWtV76UvL5dWrNweQ'
const meta = { name, type, description: 'more information about the database' }
const type = 'manifest'
const accessController = 'test/default-access-controller'
const expectedHash = 'zdpuAmegd2PpDfTQRVhGiATCkWQDvp3JygT9WksWgJkG2u313'
const meta = { name, description: 'more information about the database' }
const { hash, manifest } = await Manifest(storage, name, type, accessController, { meta })
const { hash, manifest } = await Manifest({ storage, name, type, accessController, meta })
strictEqual(hash, expectedHash)
deepStrictEqual(manifest.meta, meta)
@ -56,7 +55,7 @@ describe('Manifest', () => {
let err
try {
await Manifest()
await Manifest({})
} catch (e) {
err = e.toString()
}
@ -68,7 +67,7 @@ describe('Manifest', () => {
let err
try {
await Manifest(storage)
await Manifest({ storage })
} catch (e) {
err = e.toString()
}
@ -80,7 +79,7 @@ describe('Manifest', () => {
let err
try {
await Manifest(storage, 'manifest')
await Manifest({ storage, name: 'manifest' })
} catch (e) {
err = e.toString()
}
@ -88,15 +87,15 @@ describe('Manifest', () => {
strictEqual(err, 'Error: type is required')
})
it('throws an error if accessControllerAddress is not specified', async () => {
it('throws an error if accessController is not specified', async () => {
let err
try {
await Manifest(storage, 'manifest', 'manifest-test')
await Manifest({ storage, name: 'manifest', type: 'manifest' })
} catch (e) {
err = e.toString()
}
strictEqual(err, 'Error: accessControllerAddress is required')
strictEqual(err, 'Error: accessController is required')
})
})

View File

@ -0,0 +1,115 @@
import { strictEqual, deepStrictEqual, notStrictEqual } from 'assert'
import rmrf from 'rimraf'
import * as IPFS from 'ipfs-core'
import OrbitDB, { AccessControllers } from '../src/OrbitDB.js'
import config from './config.js'
import pathJoin from '../src/utils/path-join.js'
const type = 'custom!'
const CustomAccessController = () => async ({ orbitdb, identities, address }) => {
address = pathJoin('/', type, 'controller')
return {
address
}
}
CustomAccessController.type = type
describe('Add a custom access controller', function () {
this.timeout(5000)
let ipfs
let orbitdb
before(async () => {
ipfs = await IPFS.create({ ...config.daemon1, repo: './ipfs1' })
orbitdb = await OrbitDB({ ipfs })
})
after(async () => {
if (orbitdb) {
await orbitdb.stop()
}
if (ipfs) {
await ipfs.stop()
}
// Remove the added custom database type from OrbitDB import
AccessControllers.remove(type)
await rmrf('./orbitdb')
await rmrf('./ipfs1')
})
describe('Default supported access controllers', function () {
it('returns default supported access controllers', async () => {
const expected = [
'ipfs',
'orbitdb'
]
deepStrictEqual(Object.keys(AccessControllers.types), expected)
})
it('throws and error if custom access controller hasn\'t been added', async () => {
let err
try {
const db = await orbitdb.open('hello', { AccessController: CustomAccessController() })
await db.close()
await orbitdb.open(db.address)
} catch (e) {
err = e
}
notStrictEqual(err, undefined)
strictEqual(err.message, 'AccessController type \'custom!\' is not supported')
})
})
describe('Custom access controller', function () {
before(() => {
AccessControllers.add(CustomAccessController)
})
it('create a database with the custom access controller', async () => {
const name = 'hello custom AC'
const db = await orbitdb.open(name, { AccessController: CustomAccessController() })
strictEqual(db.access.address, '/custom!/controller')
})
it('throws and error if custom access controller already exists', async () => {
let err
try {
AccessControllers.add(CustomAccessController)
} catch (e) {
err = e.toString()
}
strictEqual(err, 'Error: Access controller \'custom!\' already added.')
})
it('returns custom access controller after adding it', async () => {
const expected = [
'ipfs',
'orbitdb',
type
]
deepStrictEqual(Object.keys(AccessControllers.types), expected)
})
it('can be removed from supported access controllers', async () => {
const expected = [
'ipfs',
'orbitdb'
]
AccessControllers.remove(type)
deepStrictEqual(Object.keys(AccessControllers.types), expected)
})
})
})

View File

@ -61,7 +61,7 @@ describe('Add a custom database type', function () {
err = e
}
notStrictEqual(err, undefined)
strictEqual(err.message, 'Unspported database type: \'custom!\'')
strictEqual(err.message, 'Unsupported database type: \'custom!\'')
})
})

View File

@ -6,6 +6,8 @@ import OrbitDB from '../src/OrbitDB.js'
import config from './config.js'
import waitFor from './utils/wait-for.js'
import connectPeers from './utils/connect-nodes.js'
import IPFSAccessController from '../src/access-controllers/ipfs.js'
import OrbitDBAccessController from '../src/access-controllers/orbitdb.js'
const dbPath = './orbitdb/tests/write-permissions'
@ -46,7 +48,7 @@ describe('Write Permissions', function () {
await rmrf('./ipfs2')
})
it('throws an error if a peer writes to a log with default write access', async () => {
it('throws an error if another peer writes to a log with default write access', async () => {
let err
let connected = false
@ -87,7 +89,7 @@ describe('Write Permissions', function () {
++updateCount
}
const db1 = await orbitdb1.open('write-test', { write: ['*'] })
const db1 = await orbitdb1.open('write-test', { AccessController: IPFSAccessController({ write: ['*'] }) })
const db2 = await orbitdb2.open(db1.address)
db2.events.on('join', onConnected)
@ -111,13 +113,14 @@ describe('Write Permissions', function () {
let updateCount = 0
const options = {
// Set write access for both clients
write: [
orbitdb1.identity.id,
orbitdb2.identity.id
]
AccessController: IPFSAccessController({
// Set write access for both clients
write: [
orbitdb1.identity.id,
orbitdb2.identity.id
]
})
}
const onConnected = async (peerId, heads) => {
connected = true
}
@ -150,9 +153,11 @@ describe('Write Permissions', function () {
let connected = false
const options = {
write: [
orbitdb1.identity.id
]
AccessController: IPFSAccessController({
write: [
orbitdb1.identity.id
]
})
}
const onConnected = async (peerId, heads) => {
@ -179,4 +184,38 @@ describe('Write Permissions', function () {
await db1.close()
await db2.close()
})
it('uses an OrbitDB access controller to manage access', async () => {
let connected = false
let updateCount = 0
const onConnected = async (peerId, heads) => {
connected = true
}
const onUpdate = async (entry) => {
++updateCount
}
const db1 = await orbitdb1.open('write-test', { AccessController: OrbitDBAccessController() })
const db2 = await orbitdb2.open(db1.address, { AccessController: OrbitDBAccessController() })
db2.events.on('join', onConnected)
db2.events.on('update', onUpdate)
await waitFor(() => connected, () => true)
await db1.access.grant('write', db2.identity.id)
await db2.access.grant('write', db1.identity.id)
await db1.add('record 1')
await db2.add('record 2')
await waitFor(() => updateCount === 2, () => true)
strictEqual((await db1.all()).length, (await db2.all()).length)
await db1.close()
await db2.close()
})
})