mirror of
https://github.com/openwrt/luci.git
synced 2025-01-31 06:01:47 +00:00
22d48305af
Signed-off-by: Sergey Ponomarev <stokito@gmail.com>
340 lines
10 KiB
JavaScript
340 lines
10 KiB
JavaScript
'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('table', { 'class': 'table' }, [
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('th', { 'class': 'th' }, [ _('ACL group') ]),
|
|
E('th', { 'class': 'th' }, [ _('Description') ]),
|
|
E('th', { 'class': 'th' }, [ _('Access level') ])
|
|
]),
|
|
E('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, [ '' ]),
|
|
E('td', { 'class': 'td' }, [ '' ]),
|
|
E('td', { '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('tr', { 'class': 'tr' }, [
|
|
E('td', { 'class': 'td' }, [ aclGroupName ]),
|
|
E('td', { 'class': 'td' }, [ aclList[aclGroupName].description || '-' ]),
|
|
E('td', { '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) {
|
|
uci.unset('rpcd', section_id, 'read');
|
|
uci.unset('rpcd', section_id, 'write');
|
|
|
|
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];
|
|
}
|
|
}
|
|
|
|
let 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.cfgvalue = function(section_id) {
|
|
var value = uci.get('rpcd', section_id, 'password') || '';
|
|
|
|
if (value.substring(0, 3) == '$p$')
|
|
return 'shadow';
|
|
else
|
|
return 'crypted';
|
|
};
|
|
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.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', _('Access 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();
|
|
}
|
|
});
|