121 lines
5.0 KiB
Python
Executable File
121 lines
5.0 KiB
Python
Executable File
#!/usr/bin/python3
|
|
"""Manage the groups of the router's directory from the command line, with vpnrouter_users.
|
|
|
|
A group has a name, an optional description and at least one member, each a user of the
|
|
directory. The answer is text for people on standard output, and a failure is a message on
|
|
standard error. With --json the answer is JSON on standard output, and a failure is
|
|
{"error": ...} there too. The exit status says what failed: 2 the request is invalid, 3 the
|
|
group or the user does not exist, 4 the group exists, 1 anything else. -h or --help shows the
|
|
commands and their options.
|
|
|
|
vpn-router-group [--json] list
|
|
vpn-router-group [--json] add <name> --member U [--member U ...] [--description D]
|
|
vpn-router-group [--json] update <name> [--name N] [--description D]
|
|
vpn-router-group [--json] remove <name>
|
|
vpn-router-group [--json] member-add <name> <user>
|
|
vpn-router-group [--json] member-remove <name> <user>
|
|
"""
|
|
|
|
import argparse
|
|
import json
|
|
import sys
|
|
|
|
sys.path.insert(0, '/usr/lib/vpn-router')
|
|
|
|
import ldap
|
|
|
|
import vpnrouter_users
|
|
from vpnrouter_users import Failure
|
|
|
|
#: The help line of each command.
|
|
HELP = {
|
|
'list': 'list the groups with their members',
|
|
'add': 'add a group with at least one member',
|
|
'update': 'rename the group or change its description',
|
|
'remove': 'remove the group; its members are kept',
|
|
'member-add': 'add a user to the group',
|
|
'member-remove': 'take a user out of the group; the last member cannot leave',
|
|
}
|
|
|
|
|
|
class Parser(argparse.ArgumentParser):
|
|
"""An argument parser whose errors are Failures with status 2, so that --json answers them as JSON."""
|
|
|
|
def error(self, message):
|
|
raise Failure(f'{message}; see {self.prog} --help', 2)
|
|
|
|
|
|
def parse(argv):
|
|
parser = Parser(prog='vpn-router-group', description='Manage the groups of the router.')
|
|
parser.add_argument('--json', action='store_true', help='answer in JSON')
|
|
commands = parser.add_subparsers(dest='command', required=True, metavar='command', parser_class=Parser)
|
|
for command, text in HELP.items():
|
|
sub = commands.add_parser(command, help=text, description=text[0].upper() + text[1:] + '.')
|
|
if command != 'list':
|
|
sub.add_argument('name', help='the group name')
|
|
if command == 'add':
|
|
sub.add_argument('--member', dest='members', action='append', required=True, metavar='USER',
|
|
help='a member; give it once for each member')
|
|
sub.add_argument('--description', metavar='TEXT', help='the description')
|
|
if command == 'update':
|
|
sub.add_argument('--name', dest='new_name', metavar='NAME', help='the new group name')
|
|
sub.add_argument('--description', metavar='TEXT', help="the description; '' removes it")
|
|
if command in ('member-add', 'member-remove'):
|
|
sub.add_argument('user', help='the user name')
|
|
return parser.parse_args(argv)
|
|
|
|
|
|
def run(connection, options):
|
|
"""The answer of the command the options name."""
|
|
command = options.command
|
|
if command == 'list':
|
|
return vpnrouter_users.list_groups(connection)
|
|
if command == 'add':
|
|
return vpnrouter_users.group_add(connection, options.name, options.description, options.members)
|
|
if command == 'update':
|
|
return vpnrouter_users.group_update(connection, options.name, options.new_name, options.description)
|
|
if command == 'remove':
|
|
return vpnrouter_users.group_remove(connection, options.name)
|
|
if command == 'member-add':
|
|
return vpnrouter_users.group_member_add(connection, options.name, options.user)
|
|
return vpnrouter_users.group_member_remove(connection, options.name, options.user)
|
|
|
|
|
|
def text_of(answer):
|
|
"""The answer as text for people."""
|
|
if answer.get('removed'):
|
|
return f'group {answer["name"]} removed'
|
|
groups = answer['groups'] if 'groups' in answer else [answer]
|
|
if not groups:
|
|
return 'no groups'
|
|
head = ('GROUP', 'MEMBERS', 'DESCRIPTION')
|
|
rows = [(group['name'], ', '.join(group['members']), group['description'] or '-') for group in groups]
|
|
widths = [max(len(row[column]) for row in (head, *rows)) for column in range(len(head))]
|
|
return '\n'.join(' '.join(cell.ljust(width) for cell, width in zip(row, widths)).rstrip() for row in (head, *rows))
|
|
|
|
|
|
def main(argv):
|
|
# A request that cannot be parsed is answered in JSON when --json is among the arguments.
|
|
as_json = '--json' in argv
|
|
try:
|
|
options = parse(argv)
|
|
as_json = options.json
|
|
with vpnrouter_users.session() as connection:
|
|
answer = run(connection, options)
|
|
except Failure as failure:
|
|
message, status = str(failure), failure.status
|
|
except (ldap.LDAPError, OSError) as exc:
|
|
message, status = f'the user directory failed: {exc}', 1
|
|
else:
|
|
print(json.dumps(answer) if as_json else text_of(answer))
|
|
return 0
|
|
if as_json:
|
|
print(json.dumps({'error': message}))
|
|
else:
|
|
print(f'vpn-router-group: {message}', file=sys.stderr)
|
|
return status
|
|
|
|
|
|
if __name__ == '__main__':
|
|
sys.exit(main(sys.argv[1:]))
|