summaryrefslogtreecommitdiffhomepage
path: root/applications/luci-app-acl/htdocs/luci-static/resources/view/system/acl.js
diff options
context:
space:
mode:
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.js340
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();
+ }
+});