Skip to content

Commit 7b8b8f6

Browse files
authored
Merge pull request SolidOS#71 from solid/chat
ACL control adds web app control; Chat
2 parents b916078 + 2e30be0 commit 7b8b8f6

17 files changed

Lines changed: 2051 additions & 1289 deletions

src/acl-control.js

Lines changed: 184 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,10 @@
55
// Without this dropping anything onto a browser page will cause chrome etc to jump to diff page
66
// throwing away all the user's work.
77

8-
/* global alert */
8+
/* global alert window */
99
var UI = {}
1010

11+
UI.authn = require('./signin')
1112
UI.acl = require('./acl')
1213
UI.icons = require('./iconBase')
1314
UI.ns = require('./ns')
@@ -81,11 +82,13 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
8182

8283
// A world button can be dragged to gve public access.
8384
// later, allow it to be pressed to make pubicly viewable?
84-
var publicAccessCell = bottomRow.appendChild(dom.createElement('td'))
85-
var publicAccessButton = publicAccessCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_98053.svg', 'Public'))
86-
UI.widgets.makeDraggable(publicAccessButton, UI.ns.foaf('Agent')) // Represent everyone
85+
var bottomLeftCell = bottomRow.appendChild(dom.createElement('td'))
86+
// var bottomMiddleCell = bottomRow.appendChild(dom.createElement('td'))
87+
var bottomRightCell = bottomRow.appendChild(dom.createElement('td'))
8788

88-
var bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;'
89+
// var publicAccessButton = bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_98053.svg', 'Public'))
90+
91+
const bigButtonStyle = 'border-radius: 0.3em; background-color: white; border: 0.1em solid #888;'
8992

9093
// This is the main function which produces an editable access control.
9194
// There are two of these in all iff the defaults are separate
@@ -156,13 +159,18 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
156159
if (uri.startsWith('http') && uri.split('/').length === 3) { // there is no third slash
157160
return {pred: 'origin', obj: obj} // The only way to know an origin alas
158161
}
162+
// @@ This is an almighty kludge needed because drag and drop adds extra slashes to origins
163+
if (uri.startsWith('http') && uri.split('/').length === 4 && uri.endsWith('/')) { // there IS third slash
164+
console.log('Assuming final slash on dragged origin URI was unintended!')
165+
return {pred: 'origin', obj: $rdf.sym(uri.slice(0, -1))} // Fix a URI where the drag and drop system has added a spurious slash
166+
}
159167

160168
if (ns.vcard('WebID').uri in types) return {pred: 'agent', obj: obj}
161169

162170
if (ns.vcard('Group').uri in types) {
163171
return {pred: 'agentGroup', obj: obj} // @@ note vcard membership not RDFs
164172
}
165-
if (obj.sameTerm(ns.foaf('Agent')) ||
173+
if (obj.sameTerm(ns.foaf('Agent')) || obj.sameTerm(ns.acl('AuthenticatedAgent')) || // AuthenticatedAgent
166174
obj.sameTerm(ns.rdf('Resource')) || obj.sameTerm(ns.owl('Thing'))) {
167175
return {pred: 'agentClass', obj: obj}
168176
}
@@ -328,9 +336,14 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
328336
}
329337

330338
var res = agentTriage(u) // eg 'agent', 'origin', agentClass'
339+
const thing = $rdf.sym(u)
331340
if (!res) {
332-
console.log(' looking up dropped thing ' + u)
333-
await kb.fetcher.load(u)
341+
console.log(' Not obvious: looking up dropped thing ' + thing)
342+
try {
343+
await kb.fetcher.load(thing.doc())
344+
} catch (err) {
345+
console.log('Ignore error looking up dropped thing: ' + err)
346+
}
334347
res = agentTriage(u)
335348
if (!res) {
336349
console.log(' Error: Drop fails to drop appropriate thing! ' + u)
@@ -342,10 +355,18 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
342355
}
343356
}// handleOneDroppedURI
344357

358+
async function addNewUIRI (uri) {
359+
await handleOneDroppedURI(uri)
360+
saveAndRestoreUI()
361+
}
362+
345363
if (options.modify) {
364+
row.addNewURI = addNewUIRI
346365
UI.widgets.makeDropTarget(row, handleManyDroppedURIs)
347366
}
348-
}
367+
return row
368+
} // renderCombo
369+
349370
var syncPanel = function () {
350371
var kids = box.children
351372
for (var i = 0; i < kids.length; i++) {
@@ -355,13 +376,160 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
355376
} // @@ later -- need to addd combos not in the box?
356377
}
357378

358-
var k, combo
379+
function renderAdditionTool (ele, lastRow) {
380+
const ns = UI.ns
381+
function removeOthers (button) {
382+
button.keep = true
383+
button.parentNode.keep = true
384+
var removeThese = []
385+
for (var ele of bar.children) {
386+
if (!ele.keep) removeThese.push(ele)
387+
}
388+
removeThese.forEach(e => bar.removeChild(e))
389+
}
390+
391+
function removeBar () {
392+
ele.removeChild(ele.bar)
393+
ele.bar = null
394+
}
395+
if (ele.bar) { // toggle
396+
return removeBar()
397+
}
398+
const bar = ele.appendChild(dom.createElement('div'))
399+
ele.bar = bar
400+
401+
/** Buttons to add different types of theings to have access
402+
*/
403+
404+
// Person
405+
bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Individual'], 'Add Person', async event => {
406+
removeOthers(event.target)
407+
let name = await UI.widgets.askName(dom, kb, bar, ns.vcard('URI'), ns.vcard('Individual'), 'person')
408+
if (!name) return removeBar() // user cancelled
409+
const domainNameRegexp = /^https?:/i
410+
if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element
411+
return alert('Not a http URI')
412+
}
413+
// @@ check it actually is a person and has an owner who agrees they own it
414+
console.log('Adding to ACL person: ' + name)
415+
await lastRow.addNewURI(name)
416+
removeBar()
417+
}))
418+
419+
// Group
420+
bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['vcard:Group'], 'Add Group', async event => {
421+
removeOthers(event.target)
422+
let name = await UI.widgets.askName(dom, kb, bar, ns.vcard('URI'), ns.vcard('Group'), 'group')
423+
if (!name) return removeBar() // user cancelled
424+
const domainNameRegexp = /^https?:/i
425+
if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element
426+
return alert('Not a http URI')
427+
}
428+
// @@ check it actually is a group and has an owner who agrees they own it
429+
console.log('Adding to ACL group: ' + name)
430+
await lastRow.addNewURI(name)
431+
removeBar()
432+
}))
433+
434+
// General public
435+
bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + UI.widgets.iconForClass['foaf:Agent'], 'Add Everyone', async event => {
436+
statusBlock.textContent = 'Adding the general public to those who can read. Drag the globe to a different level to give them more access.'
437+
await lastRow.addNewURI(ns.foaf('Agent').uri)
438+
removeBar()
439+
}))
440+
441+
// AuthenticatedAgent
442+
bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_99101.svg', 'Anyone logged In', async event => {
443+
statusBlock.textContent = 'Adding the anyone logged in to those who can read. Drag the ID icon to a different level to give them more access.'
444+
await lastRow.addNewURI(ns.acl('AuthenticatedAgent').uri)
445+
removeBar()
446+
}))
447+
448+
// Bots
449+
bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_Robot_849764.svg', 'A Software Agent (bot)', async event => {
450+
removeOthers(event.target)
451+
let name = await UI.widgets.askName(dom, kb, bar, ns.vcard('URI'), ns.schema('Application'), 'bot')
452+
if (!name) return removeBar() // user cancelled
453+
const domainNameRegexp = /^https?:/i
454+
if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element
455+
return alert('Not a http URI')
456+
}
457+
// @@ check it actually is a bot and has an owner who agrees they own it
458+
console.log('Adding to ACL bot: ' + name)
459+
await lastRow.addNewURI(name)
460+
removeBar()
461+
}))
462+
463+
// Web Apps
464+
bar.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_15177.svg', 'A Web App (origin)', async event => {
465+
removeOthers(event.target)
466+
var context = {div: bar, dom}
467+
await UI.authn.logInLoadProfile(context)
468+
var trustedApps = kb.each(context.me, ns.acl('trustedApp'))
469+
var trustedOrigins = trustedApps.flatMap(app => kb.each(app, ns.acl('origin')))
470+
471+
bar.appendChild(dom.createElement('p')).textContent = `You have ${trustedOrigins.length} selected web apps.`
472+
var table = bar.appendChild(dom.createElement('table'))
473+
trustedApps.forEach(app => {
474+
const origin = kb.any(app, ns.acl('origin'))
475+
var thingTR = UI.widgets.personTR(dom, ns.acl('origin'), origin, {})
476+
var innerTable = dom.createElement('table')
477+
var innerRow = innerTable.appendChild(dom.createElement('tr'))
478+
var innerLeft = innerRow.appendChild(dom.createElement('td'))
479+
var innerMiddle = innerRow.appendChild(dom.createElement('td'))
480+
var innerRight = innerRow.appendChild(dom.createElement('td'))
481+
innerLeft.appendChild(thingTR)
482+
innerMiddle.textContent = 'Give access to ' + noun + ' ' + UI.utils.label(subject) + '?'
483+
innerRight.appendChild(UI.widgets.continueButton(dom, async event => {
484+
await lastRow.addNewURI(origin.uri)
485+
}))
486+
table.appendChild(innerTable)
487+
})
488+
table.style = 'margin: em; background-color: #eee;'
489+
490+
// Add the Trusted App pane for managing you set of apps
491+
var trustedAppControl = window.panes.trustedApplications.render(context.me, dom, {})
492+
trustedAppControl.style.borderColor = 'orange'
493+
trustedAppControl.style.borderWidth = '0.1em'
494+
trustedAppControl.style.borderRadius = '1em'
495+
bar.appendChild(trustedAppControl)
496+
const cancel = UI.widgets.cancelButton(dom, () => bar.removeChild(trustedAppControl))
497+
trustedAppControl.insertBefore(cancel, trustedAppControl.firstChild)
498+
cancel.style.float = 'right'
499+
500+
let name = await UI.widgets.askName(dom, kb, bar, null, ns.schema('WebApplication'), 'webapp domain') // @@ hack
501+
if (!name) return removeBar() // user cancelled
502+
const domainNameRegexp = /^([a-z0-9]+(-[a-z0-9]+)*\.)+[a-z]{2,}$/i
503+
// https://www.oreilly.com/library/view/regular-expressions-cookbook/9781449327453/ch08s15.html
504+
if (!name.match(domainNameRegexp)) { // @@ enforce in user input live like a form element
505+
return alert('Not a domain name')
506+
}
507+
const origin = 'https://' + name
508+
console.log('Adding to ACL origin: ' + origin)
509+
await lastRow.addNewURI(origin)
510+
removeBar()
511+
}))
512+
}
513+
514+
function renderAddToolBar (box, lastRow) {
515+
// const toolRow = box.appendChild(dom.createElement('tr'))
516+
bottomLeftCell.appendChild(UI.widgets.button(dom, UI.icons.iconBase + 'noun_34653_green.svg', 'Add ...', event => {
517+
renderAdditionTool(bottomLeftCell, lastRow)
518+
}))
519+
}
520+
521+
var k, combo, lastRow
359522
for (k = 15; k > 0; k--) {
360523
combo = kToCombo(k)
361524
if ((options.modify && recommended[k]) || byCombo[combo]) {
362-
renderCombo(byCombo, combo)
525+
lastRow = renderCombo(byCombo, combo)
363526
} // if
364527
} // for
528+
529+
if (options.modify) {
530+
renderAddToolBar(box, lastRow)
531+
}
532+
365533
return byCombo
366534
} // ACLControlEditable
367535

@@ -389,7 +557,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
389557
ACLControlEditable(box, doc, targetACLDoc, kb2, {modify: false}) // Add btton to save them as actual
390558
box.style.cssText = 'color: #777;'
391559

392-
var editPlease = bottomRow.appendChild(dom.createElement('button'))
560+
var editPlease = bottomRightCell.appendChild(dom.createElement('button'))
393561
editPlease.textContent = 'Set specific sharing\nfor this ' + noun
394562
editPlease.style.cssText = bigButtonStyle
395563
editPlease.addEventListener('click', function (event) {
@@ -399,9 +567,11 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
399567
statusBlock.textContent += ' (Error writing back access control file: ' + message + ')'
400568
} else {
401569
kb.add(kb2.statements)
570+
kb.fetcher.requested[targetACLDoc.uri] = 'done' // cheat
571+
// kb.fetcher.load(targetACLDoc, {force: true})
402572
statusBlock.textContent = ' (Now editing specific access for this ' + noun + ')'
403573
// box.style.cssText = 'color: black;'
404-
bottomRow.removeChild(editPlease)
574+
bottomRightCell.removeChild(editPlease)
405575
renderBox()
406576
}
407577
})
@@ -410,7 +580,7 @@ UI.aclControl.ACLControlBox5 = function (subject, dom, noun, kb, callback) {
410580
} else { // Not using defaults
411581
var useDefault
412582
var addDefaultButton = function (prospectiveDefaultHolder) {
413-
useDefault = bottomRow.appendChild(dom.createElement('button'))
583+
useDefault = bottomRightCell.appendChild(dom.createElement('button'))
414584
useDefault.textContent = 'Stop specific sharing for this ' + noun +
415585
' -- just use default' // + UI.utils.label(thisDefaultHolder)
416586
if (prospectiveDefaultHolder) {

0 commit comments

Comments
 (0)