diff --git a/openwebrx-admin.py b/openwebrx-admin.py new file mode 100755 index 0000000..c1f6caa --- /dev/null +++ b/openwebrx-admin.py @@ -0,0 +1,6 @@ +#!/usr/bin/env python3 + +from owrxadmin.__main__ import main + +if __name__ == "__main__": + main() diff --git a/owrx/users.py b/owrx/users.py index 0398108..6c4e518 100644 --- a/owrx/users.py +++ b/owrx/users.py @@ -1,4 +1,5 @@ from abc import ABC, abstractmethod +from owrx.config import CoreConfig import json import logging @@ -19,17 +20,32 @@ class Password(ABC): return CleartextPassword(d) raise PasswordException("invalid passord encoding: {0}".format(d["type"])) - def __init__(self, pwinfo: dict): - self.pwinfo = pwinfo - @abstractmethod def is_valid(self, inp: str): pass + @abstractmethod + def toJson(self): + pass + class CleartextPassword(Password): + def __init__(self, pwinfo): + if isinstance(pwinfo, str): + self._value = pwinfo + elif isinstance(pwinfo, dict): + self._value = pwinfo["value"] + else: + raise ValueError("invalid argument to ClearTextPassword()") + def is_valid(self, inp: str): - return self.pwinfo["value"] == inp + return self._value == inp + + def toJson(self): + return { + "encoding": "string", + "value": self._value + } class User(object): @@ -38,6 +54,13 @@ class User(object): self.enabled = enabled self.password = password + def toJson(self): + return { + "user": self.name, + "enabled": self.enabled, + "password": self.password.toJson() + } + class UserList(object): sharedInstance = None @@ -51,30 +74,53 @@ class UserList(object): def __init__(self): self.users = self._loadUsers() + def _getUsersFile(self): + config = CoreConfig() + return "{data_directory}/users.json".format(data_directory=config.get_data_directory()) + def _loadUsers(self): - for file in ["/etc/openwebrx/users.json", "users.json"]: - try: - f = open(file, "r") + usersFile = self._getUsersFile() + try: + with open(usersFile, "r") as f: users_json = json.load(f) - f.close() - return {u.name: u for u in [self.buildUser(d) for d in users_json]} - except FileNotFoundError: - pass - except json.JSONDecodeError: - logger.exception("error while parsing users file %s", file) - return {} - except Exception: - logger.exception("error while processing users from %s", file) - return {} - return {} + return {u.name: u for u in [self._jsonToUser(d) for d in users_json]} + except FileNotFoundError: + return {} + except json.JSONDecodeError: + logger.exception("error while parsing users file %s", usersFile) + return {} + except Exception: + logger.exception("error while processing users from %s", usersFile) + return {} - def buildUser(self, d): + def _jsonToUser(self, d): if "user" in d and "password" in d and "enabled" in d: return User(d["user"], d["enabled"], Password.from_dict(d["password"])) + def _userToJson(self, u): + return u.toJson() + + def _store(self): + usersFile = self._getUsersFile() + users = [u.toJson() for u in self.users.values()] + try: + with open(usersFile, "w") as f: + json.dump(users, f, indent=4) + except Exception: + logger.exception("error while writing users file %s", usersFile) + + def addUser(self, user: User): + self[user.name] = user + def __getitem__(self, item): return self.users[item] def __contains__(self, item): return item in self.users + + def __setitem__(self, key, value): + if key in self.users: + raise KeyError("User {user} already exists".format(user=key)) + self.users[key] = value + self._store() diff --git a/owrxadmin/__main__.py b/owrxadmin/__main__.py index 65c4765..227d881 100644 --- a/owrxadmin/__main__.py +++ b/owrxadmin/__main__.py @@ -1,2 +1,22 @@ +from owrx.version import openwebrx_version +from owrxadmin.commands import NewUserCommand +import argparse +import sys + + def main(): - print("OpenWebRX admin") + print("OpenWebRX admin version {version}".format(version=openwebrx_version)) + + parser = argparse.ArgumentParser() + parser.add_argument("command", help="One of the following commands: adduser, removeuser") + parser.add_argument("--noninteractive", action="store_true", help="Don't ask for any user input (useful for automation)") + parser.add_argument("-u", "--user") + args = parser.parse_args() + + if args.command == "adduser": + NewUserCommand().run(args) + elif args.command == "removeuser": + print("removing user") + else: + print("Unknown command: {command}".format(command=args.command)) + sys.exit(1) diff --git a/owrxadmin/commands.py b/owrxadmin/commands.py new file mode 100644 index 0000000..182a10e --- /dev/null +++ b/owrxadmin/commands.py @@ -0,0 +1,46 @@ +from abc import ABC, abstractmethod +from getpass import getpass +from owrx.users import UserList, User, CleartextPassword +import sys +import random +import string + + +class Command(ABC): + @abstractmethod + def run(self, args): + pass + + +class NewUserCommand(Command): + def run(self, args): + if args.user: + username = args.user + else: + if args.noninteractive: + print("ERROR: User name not specified") + sys.exit(1) + else: + username = input("Please enter the user name: ") + if args.noninteractive: + print("Generating password for user {username}...".format(username=username)) + password = self.getRandomPassword() + print('Password for {username} is "{password}".'.format(username=username, password=password)) + # TODO implement this threat + print('This password is suitable for initial setup only, you will be asked to reset it on initial use.') + print('This password cannot be recovered from the system, please note it down now.') + else: + password = getpass("Please enter the password for {username}: ".format(username=username)) + confirm = getpass("Please confirm password: ") + if password != confirm: + print("ERROR: Password mismatch.") + sys.exit(1) + + print("Creating user {username}...".format(username=username)) + userList = UserList() + user = User(name=username, enabled=True, password=CleartextPassword(password)) + userList.addUser(user) + + def getRandomPassword(self, length=10): + printable = list(string.ascii_letters) + list(string.digits) + return ''.join(random.choices(printable, k=length)) diff --git a/users.json b/users.json deleted file mode 100644 index 298d7f2..0000000 --- a/users.json +++ /dev/null @@ -1,11 +0,0 @@ -[ - { - "user": "admin", - "password": { - "encoding": "string", - "value": "password", - "force_change": true - }, - "enabled": true - } -] \ No newline at end of file