diff options
Diffstat (limited to 'applications/luci-app-acl/htdocs/luci-static/resources/view/system/acl.js')
-rw-r--r-- | applications/luci-app-acl/htdocs/luci-static/resources/view/system/acl.js | 340 |
1 files changed, 340 insertions, 0 deletions
diff --git a/applications/luci-app-acl/htdocs/luci-static/resources/view/system/acl.js b/applications/luci-app-acl/htdocs/luci-static/resources/view/system/acl.js new file mode 100644 index 000000000..93a068c74 --- /dev/null +++ b/applications/luci-app-acl/htdocs/luci-static/resources/view/system/acl.js @@ -0,0 +1,340 @@ +'use strict'; +'require view'; +'require dom'; +'require fs'; +'require ui'; +'require uci'; +'require form'; +'require tools.widgets as widgets'; + +var aclList = {}; + +function globListToRegExp(section_id, option) { + var list = L.toArray(uci.get('rpcd', section_id, option)), + positivePatterns = [], + negativePatterns = []; + + if (option == 'read') + list.push.apply(list, L.toArray(uci.get('rpcd', section_id, 'write'))); + + for (var i = 0; i < list.length; i++) { + var array, glob; + + if (list[i].match(/^\s*!/)) { + glob = list[i].replace(/^\s*!/, '').trim(); + array = negativePatterns; + } + else { + glob = list[i].trim(), + array = positivePatterns; + } + + array.push(glob.replace(/[.*+?^${}()|[\]\\]/g, function(m) { + switch (m[0]) { + case '?': + return '.'; + + case '*': + return '.*'; + + default: + return '\\' + m[0]; + } + })); + } + + return [ + new RegExp('^' + (positivePatterns.length ? '(' + positivePatterns.join('|') + ')' : '') + '$'), + new RegExp('^' + (negativePatterns.length ? '(' + negativePatterns.join('|') + ')' : '') + '$') + ]; +} + +var cbiACLLevel = form.DummyValue.extend({ + textvalue: function(section_id) { + var allowedAclMatches = globListToRegExp(section_id, this.option.match(/read/) ? 'read' : 'write'), + aclGroupNames = Object.keys(aclList), + matchingGroupNames = []; + + for (var j = 0; j < aclGroupNames.length; j++) + if (allowedAclMatches[0].test(aclGroupNames[j]) && !allowedAclMatches[1].test(aclGroupNames[j])) + matchingGroupNames.push(aclGroupNames[j]); + + if (matchingGroupNames.length == aclGroupNames.length) + return E('span', { 'class': 'label' }, [ _('full', 'All permissions granted') ]); + else if (matchingGroupNames.length > 0) + return E('span', { 'class': 'label' }, [ _('partial (%d/%d)', 'Some permissions granted').format(matchingGroupNames.length, aclGroupNames.length) ]); + else + return E('span', { 'class': 'label warning' }, [ _('denied', 'No permissions granted') ]); + } +}); + +var cbiACLSelect = form.Value.extend({ + renderWidget: function(section_id) { + var readMatches = globListToRegExp(section_id, 'read'), + writeMatches = globListToRegExp(section_id, 'write'); + + var table = E('div', { 'class': 'table' }, [ + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'th' }, [ _('ACL group') ]), + E('div', { 'class': 'th' }, [ _('Description') ]), + E('div', { 'class': 'th' }, [ _('Access level') ]) + ]), + E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td' }, [ '' ]), + E('div', { 'class': 'td' }, [ '' ]), + E('div', { 'class': 'td' }, [ + _('Set all: ', 'Set all permissions in the table below to one of the given values'), + E('a', { 'href': '#', 'click': function() { + table.querySelectorAll('select').forEach(function(select) { select.value = select.options[0].value }); + } }, [ _('denied', 'No permissions granted') ]), ' | ', + E('a', { 'href': '#', 'click': function() { + table.querySelectorAll('select').forEach(function(select) { select.value = 'read' }); + } }, [ _('readonly', 'Only read permissions granted') ]), ' | ', + E('a', { 'href': '#', 'click': function() { + table.querySelectorAll('select').forEach(function(select) { select.value = 'write' }); + } }, [ _('full', 'All permissions granted') ]), + ]) + ]) + ]); + + Object.keys(aclList).sort().forEach(function(aclGroupName) { + var isRequired = (aclGroupName == 'unauthenticated' || aclGroupName == 'luci-base'), + isReadable = (readMatches[0].test(aclGroupName) && !readMatches[1].test(aclGroupName)) || null, + isWritable = (writeMatches[0].test(aclGroupName) && !writeMatches[1].test(aclGroupName)) || null; + + table.appendChild(E('div', { 'class': 'tr' }, [ + E('div', { 'class': 'td' }, [ aclGroupName ]), + E('div', { 'class': 'td' }, [ aclList[aclGroupName].description || '-' ]), + E('div', { 'class': 'td' }, [ + E('select', { 'data-acl-group': aclGroupName }, [ + isRequired ? E([]) : E('option', { 'value': '' }, [ _('denied', 'No permissions granted') ]), + E('option', { 'value': 'read', 'selected': isReadable }, [ _('readonly', 'Only read permissions granted') ]), + E('option', { 'value': 'write', 'selected': isWritable }, [ _('full', 'All permissions granted') ]) + ]) + ]) + ])); + }); + + return table; + }, + + formvalue: function(section_id) { + var node = this.map.findElement('data-field', this.cbid(section_id)), + data = {}; + + node.querySelectorAll('[data-acl-group]').forEach(function(select) { + var aclGroupName = select.getAttribute('data-acl-group'), + value = select.value; + + if (!value) + return; + + switch (value) { + case 'write': + data.write = data.write || []; + data.write.push(aclGroupName); + /* fall through */ + + case 'read': + data.read = data.read || []; + data.read.push(aclGroupName); + break; + } + }); + + return data; + }, + + write: function(section_id, value) { + if (L.isObject(value) && Array.isArray(value.read)) + uci.set('rpcd', section_id, 'read', value.read); + + if (L.isObject(value) && Array.isArray(value.write)) + uci.set('rpcd', section_id, 'write', value.write); + } +}); + +return view.extend({ + load: function() { + return L.resolveDefault(fs.list('/usr/share/rpcd/acl.d'), []).then(function(entries) { + var tasks = [ + L.resolveDefault(fs.stat('/usr/sbin/uhttpd'), null), + fs.lines('/etc/passwd') + ]; + + for (var i = 0; i < entries.length; i++) + if (entries[i].type == 'file' && entries[i].name.match(/\.json$/)) + tasks.push(L.resolveDefault(fs.read('/usr/share/rpcd/acl.d/' + entries[i].name).then(JSON.parse))); + + return Promise.all(tasks); + }); + }, + + render: function(data) { + ui.addNotification(null, E('p', [ + _('The LuCI ACL management is in an experimental stage! It does not yet work reliably with all applications') + ]), 'warning'); + + var has_uhttpd = data[0], + known_unix_users = {}; + + for (var i = 0; i < data[1].length; i++) { + var parts = data[1][i].split(/:/); + + if (parts.length >= 7) + known_unix_users[parts[0]] = true; + } + + for (var i = 2; i < data.length; i++) { + if (!L.isObject(data[i])) + continue; + + for (var aclName in data[i]) { + if (!data[i].hasOwnProperty(aclName)) + continue; + + aclList[aclName] = data[i][aclName]; + } + } + + var m, s, o; + + m = new form.Map('rpcd', _('LuCI Logins')); + + s = m.section(form.GridSection, 'login'); + s.anonymous = true; + s.addremove = true; + + s.modaltitle = function(section_id) { + return _('LuCI Logins') + ' ยป ' + (uci.get('rpcd', section_id, 'username') || _('New account')); + }; + + o = s.option(form.Value, 'username', _('Login name')); + o.rmempty = false; + + o = s.option(form.ListValue, '_variant', _('Password variant')); + o.modalonly = true; + o.value('shadow', _('Use UNIX password in /etc/shadow')); + o.value('crypted', _('Use encrypted password hash')); + o.value('plain', _('Use plain password')); + o.cfgvalue = function(section_id) { + var value = uci.get('rpcd', section_id, 'password') || ''; + + if (value.substring(0, 3) == '$p$') + return 'shadow'; + else if (value.substring(0, 3) == '$1$' || value == null) + return 'crypted'; + else + return 'plain'; + }; + o.write = function() {}; + + o = s.option(widgets.UserSelect, '_account', _('UNIX account'), _('The system account to use the password from')); + o.modalonly = true; + o.depends('_variant', 'shadow'); + o.cfgvalue = function(section_id) { + var value = uci.get('rpcd', section_id, 'password') || ''; + return value.substring(3); + }; + o.write = function(section_id, value) { + uci.set('rpcd', section_id, 'password', '$p$' + value); + }; + o.remove = function() {}; + + o = s.option(form.Value, 'password', _('Password value')); + o.modalonly = true; + o.password = true; + o.rmempty = false; + o.depends('_variant', 'crypted'); + o.depends('_variant', 'plain'); + o.cfgvalue = function(section_id) { + var value = uci.get('rpcd', section_id, 'password') || ''; + return (value.substring(0, 3) == '$p$') ? '' : value; + }; + o.validate = function(section_id, value) { + var variant = this.map.lookupOption('_variant', section_id)[0]; + + switch (value.substring(0, 3)) { + case '$p$': + return _('The password may not start with "$p$".'); + + case '$1$': + variant.getUIElement(section_id).setValue('crypted'); + break; + + default: + if (variant.formvalue(section_id) == 'crypted' && value.length && !has_uhttpd) + return _('Cannot encrypt plaintext password since uhttpd is not installed.'); + } + + return true; + }; + o.write = function(section_id, value) { + var variant = this.map.lookupOption('_variant', section_id)[0]; + + if (variant.formvalue(section_id) == 'crypted' && value.substring(0, 3) != '$1$') + return fs.exec('/usr/sbin/uhttpd', [ '-m', value ]).then(function(res) { + if (res.code == 0 && res.stdout) + uci.set('rpcd', section_id, 'password', res.stdout.trim()); + else + throw new Error(res.stderr); + }).catch(function(err) { + throw new Error(_('Unable to encrypt plaintext password: %s').format(err.message)); + }); + + uci.set('rpcd', section_id, 'password', value); + }; + o.remove = function() {}; + + o = s.option(form.Value, 'timeout', _('Session timeout')); + o.default = '300'; + o.datatype = 'uinteger'; + o.textvalue = function(section_id) { + var value = uci.get('rpcd', section_id, 'timeout') || this.default; + return +value ? '%ds'.format(value) : E('em', [ _('does not expire') ]); + }; + + o = s.option(cbiACLLevel, '_read', _('Read access')); + o.modalonly = false; + + o = s.option(cbiACLLevel, '_write', _('Write access')); + o.modalonly = false; + + o = s.option(form.ListValue, '_level', _('Acess level')); + o.modalonly = true; + o.value('write', _('full', 'All permissions granted')); + o.value('read', _('readonly', 'Only read permissions granted')); + o.value('individual', _('individual', 'Select individual permissions manually')); + o.cfgvalue = function(section_id) { + var readList = L.toArray(uci.get('rpcd', section_id, 'read')), + writeList = L.toArray(uci.get('rpcd', section_id, 'write')); + + if (writeList.length == 1 && writeList[0] == '*') + return 'write'; + else if (readList.length == 1 && readList[0] == '*') + return 'read'; + else + return 'individual'; + }; + o.write = function(section_id) { + switch (this.formvalue(section_id)) { + case 'write': + uci.set('rpcd', section_id, 'read', '*'); + uci.set('rpcd', section_id, 'write', '*'); + break; + + case 'read': + uci.set('rpcd', section_id, 'read', '*'); + uci.unset('rpcd', section_id, 'write'); + break; + } + }; + o.remove = function() {}; + + o = s.option(cbiACLSelect, '_acl'); + o.modalonly = true; + o.depends('_level', 'individual'); + + return m.render(); + } +}); |