mirror of
https://github.com/amark/gun.git
synced 2025-03-30 15:08:33 +00:00
Merge pull request #1033 from mimiza/master
SEA.certify: replace RegEx with RAD/LEX, force path to contain Certificant Pub
This commit is contained in:
commit
c17a14b53a
76
sea.js
76
sea.js
@ -654,46 +654,60 @@
|
||||
var SEA = USE('./root');
|
||||
|
||||
// This is to certify that a group of "certificants" can "put" anything at a group of matched "paths" to the certificate authority's graph
|
||||
SEA.certify = SEA.certify || (async (certificants, patterns, authority, cb, opt = {}) => { try {
|
||||
SEA.certify = SEA.certify || (async (certificants, policy = {}, authority, cb, opt = {}) => { try {
|
||||
/*
|
||||
IMPORTANT: A Certificate is like a Signature. No one knows who (authority) created/signed a cert until you put it into their graph.
|
||||
"certificants": A string (~Bobpub) || a pair || an array of pubs/pairs. These people will have the rights.
|
||||
"patterns": A string (^inbox.*), or an array of strings [^inbox.*, ^secret\-group.*]. These patterns will be used to check against soul+'/'+key
|
||||
"authority": Key pair or priv of the certificate authority
|
||||
"cb": A callback function after all things are done
|
||||
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry
|
||||
"certificants": '*' or a String (Bob.pub) || an Object that contains "pub" as a key || an array of [object || string]. These people will have the rights.
|
||||
"policy": A string ('inbox'), or a RAD/LEX object {'*': 'inbox'}, or an Array of RAD/LEX objects or strings. RAD/LEX object can contain key "?" with indexOf("*") > -1 to force key equals certificant pub. This rule is used to check against soul+'/'+key using Gun.text.match or String.match.
|
||||
"authority": Key pair or priv of the certificate authority.
|
||||
"cb": A callback function after all things are done.
|
||||
"opt": If opt.expiry (a timestamp) is set, SEA won't sync data after opt.expiry. If opt.blacklist is set, SEA will look for blacklist before syncing.
|
||||
*/
|
||||
|
||||
// We need some logic here to verify that all params are valid
|
||||
|
||||
console.log('SEA.certify() is an early experimental community supported method that may change API behavior without warning in any future version.')
|
||||
|
||||
certificants = (() => {
|
||||
var data = []
|
||||
if (certificants) {
|
||||
if ((typeof certificants === 'string' || Array.isArray(certificants)) && certificants.indexOf('*')) return '*'
|
||||
|
||||
if (typeof certificants === 'string') {
|
||||
data.push(certificants)
|
||||
return certificants
|
||||
}
|
||||
|
||||
if (Array.isArray(certificants)) {
|
||||
certificants.map(person => {
|
||||
if (typeof person ==='string') data.push(person)
|
||||
else if (typeof person === 'object' && person.pub) data.push(person.pub)
|
||||
if (certificants.length === 1 && certificants[0]) return typeof certificants[0] === 'object' && certificants[0].pub ? certificants[0].pub : typeof certificants[0] === 'string' ? certificants[0] : null
|
||||
certificants.map(certificant => {
|
||||
if (typeof certificant ==='string') data.push(certificant)
|
||||
else if (typeof certificant === 'object' && certificant.pub) data.push(certificant.pub)
|
||||
})
|
||||
}
|
||||
|
||||
if (typeof certificants === 'object' && certificants.pub) data.push(certificants.pub)
|
||||
if (typeof certificants === 'object' && certificants.pub) return certificants.pub
|
||||
|
||||
return data.length > 0 ? data : null
|
||||
}
|
||||
return data
|
||||
return null
|
||||
})()
|
||||
|
||||
patterns = patterns ? typeof patterns === 'string' ? [patterns] : Array.isArray(patterns) ? patterns : null : null
|
||||
if (!certificants) return console.log("No certificant found.")
|
||||
|
||||
const expiry = opt.expiry && (typeof opt.expiry === 'number' || typeof opt.expiry === 'string') ? parseFloat(opt.expiry) : null
|
||||
const readPolicy = (policy || {}).read ? policy.read : null
|
||||
const writePolicy = (policy || {}).write ? policy.write : typeof policy === 'string' || Array.isArray(policy) || (policy["?"] || policy["#"] || policy["."] || policy["="] || policy["*"] || policy[">"] || policy["<"]) ? policy : null
|
||||
const readBlacklist = ((opt || {}).blacklist || {}).read && (typeof opt.blacklist.read === 'string' || opt.blacklist.read['#']) ? opt.blacklist.read : null
|
||||
const writeBlacklist = typeof (opt || {}).blacklist === 'string' || (((opt || {}).blacklist || {}).write || {})['#'] ? opt.blacklist : ((opt || {}).blacklist || {}).write && (typeof opt.blacklist.write === 'string' || opt.blacklist.write['#']) ? opt.blacklist.write : null
|
||||
|
||||
if (!readPolicy && !writePolicy) return console.log("No policy found.")
|
||||
|
||||
// reserved keys: c, e, r, w, rb, wb
|
||||
const data = JSON.stringify({
|
||||
c: certificants,
|
||||
p: patterns,
|
||||
...(opt.expiry && typeof opt.expiry === 'number' ? {e: parseFloat(opt.expiry)} : {}), // inject expiry if possible
|
||||
...(opt.blacklist && typeof opt.blacklist === 'string' ? {b: opt.blacklist} : {}) // inject blacklist if possible
|
||||
...(expiry ? {e: expiry} : {}), // inject expiry if possible
|
||||
...(readPolicy ? {r: readPolicy } : {}), // "r" stands for read, which means read permission.
|
||||
...(writePolicy ? {w: writePolicy} : {}), // "w" stands for write, which means write permission.
|
||||
...(readBlacklist ? {rb: readBlacklist} : {}), // inject READ blacklist if possible
|
||||
...(writeBlacklist ? {wb: writeBlacklist} : {}), // inject WRITE blacklist if possible
|
||||
})
|
||||
|
||||
const certificate = await SEA.sign(data, authority, null, {raw:1})
|
||||
@ -1320,19 +1334,23 @@
|
||||
if (certificate.m && certificate.s && certificant && pub) {
|
||||
// now verify certificate
|
||||
return SEA.verify(certificate, pub, data => { // check if "pub" (of the graph owner) really issued this cert
|
||||
if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.")
|
||||
// "data.c" = a list of certificants/certified users, "data.p" = a list of allowed patterns
|
||||
if (u !== data && data.c && data.p && (data.c.indexOf('*') || data.c.indexOf(certificant))) {
|
||||
if (u !== data && u !== data.e && msg.put['>'] && msg.put['>'] > parseFloat(data.e)) return no("Certificate expired.") // certificate expired
|
||||
// "data.c" = a list of certificants/certified users
|
||||
// "data.w" = lex WRITE permission, in the future, there will be "data.r" which means lex READ permission
|
||||
if (u !== data && data.c && data.w && (data.c === certificant || data.c.indexOf('*' || certificant) > -1)) {
|
||||
// ok, now "certificant" is in the "certificants" list, but is "path" allowed? Check path
|
||||
let path = soul + '/' + key
|
||||
path = path.replace(path.substring(0, path.indexOf('/') + 1), '')
|
||||
for (p of data.p) {
|
||||
if (new RegExp(p).test(path)) {
|
||||
// path is allowed, but is there any blacklist? Check blacklist
|
||||
if (data.b && typeof data.b === 'string') { // "data.b" = path to the blacklist
|
||||
let path = soul.indexOf('/') > -1 ? soul.replace(soul.substring(0, soul.indexOf('/') + 1), '') : ''
|
||||
String.match = String.match || Gun.text.match
|
||||
const w = typeof data.w === 'object' || typeof data.w === 'string' ? [data.w] : Array.isArray(data.w) ? data.w : []
|
||||
for (const lex of w) {
|
||||
if ((String.match(path, lex['#']) && String.match(key, lex['.'])) || (!lex['.'] && String.match(path, lex['#'])) || (!lex['#'] && String.match(key, lex['.'])) || String.match((path ? path + '/' + key : key), lex['#'] || lex)) {
|
||||
// is Certificant forced to present in Path
|
||||
if (lex['?'] && lex['?'].indexOf('*') > -1 && path && path.indexOf(certificant) == -1 && key.indexOf(certificant) == -1) return no("Key not same as certificant pub.")
|
||||
// path is allowed, but is there any WRITE blacklist? Check it out
|
||||
if (data.wb && (typeof data.wb === 'string' || ((data.wb || {})['#']))) { // "data.wb" = path to the WRITE blacklist
|
||||
var root = user.back(-1)
|
||||
if ('~' !== data.b.slice(0,1)) root = root.get('~'+pub)
|
||||
root.get(data.b).get(certificant).once(value => {
|
||||
if (typeof data.wb === 'string' && '~' !== data.wb.slice(0, 1)) root = root.get('~' + pub)
|
||||
root.get(data.wb).get(certificant).once(value => {
|
||||
if (value && (value === 1 || value === true)) return no("Certificant blacklisted.")
|
||||
return cb(data)
|
||||
})
|
||||
|
117
test/sea/sea.js
117
test/sea/sea.js
@ -526,10 +526,11 @@ describe('SEA', function(){
|
||||
});
|
||||
});
|
||||
|
||||
it('Certify: Alice certs Bob, Bob writes to Alice', function(done){(async function(){
|
||||
it('Certify: Simple', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice)
|
||||
var dave = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, {"*": "private"}, alice)
|
||||
|
||||
user.auth(bob, () => {
|
||||
var data = Gun.state.lex()
|
||||
@ -538,73 +539,39 @@ describe('SEA', function(){
|
||||
.get("asdf")
|
||||
.get("qwerty")
|
||||
.put(data, () => {
|
||||
// Bob reads
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty").once(_data=>{
|
||||
expect(_data).to.be(data)
|
||||
user.leave()
|
||||
done()
|
||||
})
|
||||
}, { opt: { cert } })
|
||||
})
|
||||
}())})
|
||||
|
||||
it('Certify: Alice certs Bob, Bob writes to Alice, everyone reads from Alice', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice)
|
||||
|
||||
user.auth(bob, () => {
|
||||
var data = Gun.state.lex()
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty")
|
||||
.put(data, () => {
|
||||
user.leave()
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty").once(_data=>{
|
||||
expect(_data).to.be(data)
|
||||
done()
|
||||
})
|
||||
}, { opt: { cert } })
|
||||
})
|
||||
}())})
|
||||
|
||||
it('Certify: Alice certs Bob, Bob writes to Alice, Dave reads from Alice', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var dave = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice)
|
||||
|
||||
user.auth(bob, () => {
|
||||
var data = Gun.state.lex()
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty")
|
||||
.put(data, () => {
|
||||
user.leave()
|
||||
user.auth(dave, () => {
|
||||
// everyone reads
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty").once(_data=>{
|
||||
expect(_data).to.be(data)
|
||||
done()
|
||||
expect(_data).to.be(data)
|
||||
user.auth(dave, () => {
|
||||
// Dave reads
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty").once(_data=>{
|
||||
expect(_data).to.be(data)
|
||||
done()
|
||||
})
|
||||
})
|
||||
})
|
||||
})
|
||||
}, { opt: { cert } })
|
||||
})
|
||||
}())})
|
||||
|
||||
it('Certify: Simple Cert (without Expiry + Blacklist), Bob hacks Alice', function(done){(async function(){
|
||||
it('Certify: Attack', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice)
|
||||
var cert = await SEA.certify(bob, {"*": "private"}, alice)
|
||||
|
||||
user.auth(bob, () => {
|
||||
var data = Gun.state.lex()
|
||||
@ -622,8 +589,8 @@ describe('SEA', function(){
|
||||
it('Certify: Expiry', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice, null, {
|
||||
expiry: Gun.state() - 100, // expires in 100 miliseconds
|
||||
var cert = await SEA.certify(bob, {"*": "private"}, alice, null, {
|
||||
expiry: Gun.state() - 100, // expired 100 miliseconds ago
|
||||
})
|
||||
|
||||
user.auth(bob, () => {
|
||||
@ -639,36 +606,42 @@ describe('SEA', function(){
|
||||
})
|
||||
}())})
|
||||
|
||||
it('Certify: SIMPLE Blacklist', function(done){(async function(){
|
||||
it('Certify: Path must contain Certificant Pub', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice, null, {
|
||||
expiry: Gun.state() + 5000, // expires in 5 seconds
|
||||
blacklist: '~'+alice.pub+'/blacklist' // path to blacklist
|
||||
})
|
||||
var cert = await SEA.certify(bob, {"*": "private", "?": "*"}, alice)
|
||||
|
||||
user.auth(alice, async () => {
|
||||
await user.get('blacklist').get(bob.pub).put(true)
|
||||
await user.leave()
|
||||
user.auth(bob, async () => {
|
||||
var data = Gun.state.lex()
|
||||
gun.get("~" + alice.pub)
|
||||
user.auth(bob, () => {
|
||||
var data = Gun.state.lex()
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get('wrongway')
|
||||
.put(data, ack => {
|
||||
expect(ack.err).to.be.ok()
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get("asdf")
|
||||
.get("qwerty")
|
||||
.get(bob.pub)
|
||||
.get('today')
|
||||
.put(data, ack => {
|
||||
expect(ack.err).to.be.ok()
|
||||
done()
|
||||
expect(ack.ok).to.be.ok()
|
||||
gun.get("~" + alice.pub)
|
||||
.get("private")
|
||||
.get(bob.pub)
|
||||
.get('today')
|
||||
.once(_data => {
|
||||
expect(_data).to.be(data)
|
||||
done()
|
||||
})
|
||||
}, { opt: { cert } })
|
||||
})
|
||||
}, { opt: { cert } })
|
||||
})
|
||||
}())})
|
||||
|
||||
it('Certify: ADVANCED Blacklist, Alice\'s graph, Dave\'s blacklist, Bob\'s put', function(done){(async function(){
|
||||
it('Certify: Advanced - Blacklist', function(done){(async function(){
|
||||
var alice = await SEA.pair()
|
||||
var dave = await SEA.pair()
|
||||
var bob = await SEA.pair()
|
||||
var cert = await SEA.certify(bob, ["^private.*"], alice, null, {
|
||||
var cert = await SEA.certify(bob, {"*": "private"}, alice, null, {
|
||||
expiry: Gun.state() + 5000, // expires in 5 seconds
|
||||
blacklist: 'blacklist' // path to blacklist in Alice's graph
|
||||
})
|
||||
@ -678,7 +651,7 @@ describe('SEA', function(){
|
||||
await user.get('blacklist').put({'#': '~'+dave.pub+'/blacklist'})
|
||||
await user.leave()
|
||||
|
||||
// Dave logins, he add Bob to his blacklist, which is connected to the certificate that Alice issued for Bob
|
||||
// Dave logins, he adds Bob to his blacklist, which is connected to the certificate that Alice issued for Bob
|
||||
user.auth(dave, async () => {
|
||||
await user.get('blacklist').get(bob.pub).put(true)
|
||||
await user.leave()
|
||||
|
Loading…
x
Reference in New Issue
Block a user