feature: users can be enrolled for TOTP one-time passwords.
Release / release (push) Failing after 15m23s
Release / release (push) Failing after 15m23s
This commit is contained in:
1 parent
f135d776f8
commit
791fc7fddd
8 files changed
+176
-19
No files matched your search
@@ -1,7 +1,8 @@
|
||||
#!/usr/bin/python3
|
||||
"""Manage the users of the router's directory from the command line, with vpnrouter_users.
|
||||
|
||||
Passwords are read from the first line of standard input, never from the command line. The
|
||||
Passwords and TOTP secrets are read from the first line of standard input, never from the
|
||||
command line. 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 user does not exist, 4 the
|
||||
@@ -15,6 +16,9 @@ user exists, 1 anything else. -h or --help shows the commands and their options.
|
||||
vpn-router-user [--json] enable <name>
|
||||
vpn-router-user [--json] remove <name>
|
||||
vpn-router-user [--json] profile <name>
|
||||
vpn-router-user [--json] otp-new <name>
|
||||
vpn-router-user [--json] otp-enrol <name> <code> (secret on stdin)
|
||||
vpn-router-user [--json] otp-remove <name>
|
||||
"""
|
||||
|
||||
import argparse
|
||||
@@ -38,6 +42,9 @@ HELP = {
|
||||
'enable': 'let the user in again',
|
||||
'remove': 'revoke the certificate and remove the user',
|
||||
'profile': 'print the OpenVPN client profile',
|
||||
'otp-new': 'make a TOTP secret and its otpauth URI; nothing is stored',
|
||||
'otp-enrol': 'store the TOTP secret read from standard input, when the code is a code of it',
|
||||
'otp-remove': 'remove the TOTP token',
|
||||
}
|
||||
|
||||
#: The help of the person options.
|
||||
@@ -63,16 +70,18 @@ def parse(argv):
|
||||
for key, _ in vpnrouter_users.PERSON:
|
||||
sub.add_argument(f'--{key.replace("_", "-")}', dest=key, metavar='TEXT', required=command == 'add',
|
||||
help=PERSON_HELP[key])
|
||||
if command == 'otp-enrol':
|
||||
sub.add_argument('code', help='a code of the secret from the authenticator')
|
||||
return parser.parse_args(argv)
|
||||
|
||||
|
||||
def password():
|
||||
"""The password from the first line of standard input."""
|
||||
"""The password or the TOTP secret from the first line of standard input."""
|
||||
return sys.stdin.readline().rstrip('\n')
|
||||
|
||||
|
||||
def run(connection, options, secret):
|
||||
"""The answer of the command the options name; secret is the password of add and passwd."""
|
||||
"""The answer of the command the options name; secret is the password of add and passwd, the TOTP secret of otp-enrol."""
|
||||
command = options.command
|
||||
if command == 'list':
|
||||
return vpnrouter_users.list_users(connection)
|
||||
@@ -83,21 +92,26 @@ def run(connection, options, secret):
|
||||
return vpnrouter_users.update(connection, options.name, values)
|
||||
if command == 'passwd':
|
||||
return vpnrouter_users.passwd(connection, options.name, secret)
|
||||
return getattr(vpnrouter_users, command)(connection, options.name)
|
||||
if command == 'otp-enrol':
|
||||
return vpnrouter_users.otp_enrol(connection, options.name, secret, options.code)
|
||||
return getattr(vpnrouter_users, command.replace('-', '_'))(connection, options.name)
|
||||
|
||||
|
||||
def text_of(answer):
|
||||
"""The answer as text for people."""
|
||||
if 'profile' in answer:
|
||||
return answer['profile'].rstrip('\n')
|
||||
if 'secret' in answer:
|
||||
return f'secret {answer["secret"]}\nuri {answer["uri"]}'
|
||||
if answer.get('removed'):
|
||||
return f'user {answer["name"]} removed'
|
||||
users = answer['users'] if 'users' in answer else [answer]
|
||||
if not users:
|
||||
return 'no users'
|
||||
head = ('USER', 'STATE', 'CERTIFICATE', 'FIRST NAME', 'LAST NAME', 'E-MAIL')
|
||||
head = ('USER', 'STATE', 'CERTIFICATE', 'OTP', 'FIRST NAME', 'LAST NAME', 'E-MAIL')
|
||||
rows = [(user['name'], 'enabled' if user['enabled'] else 'disabled', 'issued' if user['certificate'] else 'missing',
|
||||
user['first_name'] or '-', user['last_name'] or '-', user['email'] or '-') for user in users]
|
||||
'enrolled' if user['otp'] else '-', user['first_name'] or '-', user['last_name'] or '-', user['email'] or '-')
|
||||
for user in users]
|
||||
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))
|
||||
|
||||
@@ -110,7 +124,7 @@ def main(argv):
|
||||
as_json = options.json
|
||||
if options.command != 'list':
|
||||
vpnrouter_users.check_name(options.name)
|
||||
secret = password() if options.command in ('add', 'passwd') else None
|
||||
secret = password() if options.command in ('add', 'passwd', 'otp-enrol') else None
|
||||
with vpnrouter_users.session() as connection:
|
||||
answer = run(connection, options, secret)
|
||||
except Failure as failure:
|
||||
|
||||
Reference in new issue
Block a user