diff --git a/zerver/lib/management.py b/zerver/lib/management.py index 01c6159cf2..b43df6f11d 100644 --- a/zerver/lib/management.py +++ b/zerver/lib/management.py @@ -1,11 +1,15 @@ # Library code for use in management commands +import logging from argparse import SUPPRESS, ArgumentParser, RawTextHelpFormatter +from dataclasses import dataclass from typing import Any, Dict, List, Optional from django.conf import settings -from django.core.exceptions import MultipleObjectsReturned +from django.core import validators +from django.core.exceptions import MultipleObjectsReturned, ValidationError from django.core.management.base import BaseCommand, CommandError, CommandParser +from zerver.lib.initial_password import initial_password from zerver.models import Client, Realm, UserProfile, get_client @@ -30,6 +34,13 @@ def check_config() -> None: raise CommandError(f"Error: You must set {setting_name} in /etc/zulip/settings.py.") +@dataclass +class CreateUserParameters: + email: str + full_name: str + password: Optional[str] + + class ZulipBaseCommand(BaseCommand): # Fix support for multi-line usage @@ -167,3 +178,49 @@ server via `ps -ef` or reading bash history. Prefer def get_client(self) -> Client: """Returns a Zulip Client object to be used for things done in management commands""" return get_client("ZulipServer") + + def get_create_user_params(self, options: Dict[str, Any]) -> CreateUserParameters: # nocoverage + """ + Parses parameters for user creation defined in add_create_user_args. + """ + if "email" not in options: + email = input("Email: ") + else: + email = options["email"] + + try: + validators.validate_email(email) + except ValidationError: + raise CommandError("Invalid email address.") + + if "full_name" not in options: + full_name = input("Full name: ") + else: + full_name = options["full_name"] + + if options["password_file"] is not None: + with open(options["password_file"]) as f: + password: Optional[str] = f.read().strip() + elif options["password"] is not None: + logging.warning( + "Passing password on the command line is insecure; prefer --password-file." + ) + password = options["password"] + else: + # initial_password will return a random password that + # is a salted hash of the email address in a + # development environment, and None in a production + # environment. + user_initial_password = initial_password(email) + if user_initial_password is None: + logging.info("User will be created with a disabled password.") + else: + assert settings.DEVELOPMENT + logging.info("Password will be available via `./manage.py print_initial_password`.") + password = user_initial_password + + return CreateUserParameters( + email=email, + full_name=full_name, + password=password, + ) diff --git a/zerver/management/commands/create_user.py b/zerver/management/commands/create_user.py index 7ff1e8a84c..7a72bd1322 100644 --- a/zerver/management/commands/create_user.py +++ b/zerver/management/commands/create_user.py @@ -1,15 +1,10 @@ import argparse -import logging -from typing import Any, Optional +from typing import Any -from django.conf import settings -from django.core import validators -from django.core.exceptions import ValidationError from django.core.management.base import CommandError from django.db.utils import IntegrityError from zerver.lib.actions import do_create_user -from zerver.lib.initial_password import initial_password from zerver.lib.management import ZulipBaseCommand @@ -31,48 +26,14 @@ Omit both and for interactive user creation. realm = self.get_realm(options) assert realm is not None # Should be ensured by parser - if "email" not in options: - email = input("Email: ") - else: - email = options["email"] - - try: - validators.validate_email(email) - except ValidationError: - raise CommandError("Invalid email address.") - - if "full_name" not in options: - full_name = input("Full name: ") - else: - full_name = options["full_name"] - - if options["password_file"] is not None: - with open(options["password_file"]) as f: - password: Optional[str] = f.read().strip() - elif options["password"] is not None: - logging.warning( - "Passing password on the command line is insecure; prefer --password-file." - ) - password = options["password"] - else: - # initial_password will return a random password that - # is a salted hash of the email address in a - # development environment, and None in a production - # environment. - user_initial_password = initial_password(email) - if user_initial_password is None: - logging.info("User will be created with a disabled password.") - else: - assert settings.DEVELOPMENT - logging.info("Password will be available via `./manage.py print_initial_password`.") - password = user_initial_password + create_user_params = self.get_create_user_params(options) try: do_create_user( - email, - password, + create_user_params.email, + create_user_params.password, realm, - full_name, + create_user_params.full_name, # Explicitly set tos_version=None. For servers that # have configured Terms of Service, this means that # users created via this mechanism will be prompted to