diff --git a/bin/docker_start.sh b/bin/docker_start.sh index 2516c4dd..5518b6a6 100755 --- a/bin/docker_start.sh +++ b/bin/docker_start.sh @@ -37,6 +37,18 @@ if [ -d $fixtures_dir ]; then done fi +# Create superuser +# specify password by setting OBJECTTYPE_SUPERUSER_PASSWORD in the env +# specify username by setting OBJECTTYPE_SUPERUSER_USERNAME in the env +# specify email by setting OBJECTTYPE_SUPERUSER_EMAIL in the env +if [ -n "${OBJECTTYPE_SUPERUSER_USERNAME}" ]; then + python src/manage.py createinitialsuperuser \ + --no-input \ + --username "${OBJECTTYPE_SUPERUSER_USERNAME}" \ + --email "${OBJECTTYPE_SUPERUSER_EMAIL:-admin@admin.org}" + unset OBJECTTYPE_SUPERUSER_USERNAME OBJECTTYPE_SUPERUSER_EMAIL OBJECTTYPE_SUPERUSER_PASSWORD +fi + # Start server >&2 echo "Starting server" uwsgi \ diff --git a/src/objecttypes/accounts/management/__init__.py b/src/objecttypes/accounts/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/objecttypes/accounts/management/commands/__init__.py b/src/objecttypes/accounts/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/src/objecttypes/accounts/management/commands/createinitialsuperuser.py b/src/objecttypes/accounts/management/commands/createinitialsuperuser.py new file mode 100644 index 00000000..6ea17228 --- /dev/null +++ b/src/objecttypes/accounts/management/commands/createinitialsuperuser.py @@ -0,0 +1,72 @@ +import os + +from django.conf import settings +from django.contrib.auth.management.commands.createsuperuser import ( + Command as BaseCommand, +) +from django.core.mail import send_mail +from django.urls import reverse + + +class Command(BaseCommand): + def add_arguments(self, parser): + super().add_arguments(parser) + + parser.add_argument( + "--password", + help="Set the password when the superuser is initially created.", + ) + parser.add_argument( + "--generate-password", + action="store_true", + help=( + "Generate and e-mail the password. The --password option and " + "environment variable overrule this flag." + ), + ) + + def handle(self, **options): + username = options[self.UserModel.USERNAME_FIELD] + database = options["database"] + qs = self.UserModel._default_manager.db_manager(database).filter( + **{self.UserModel.USERNAME_FIELD: username} + ) + if qs.exists(): + self.stdout.write( + self.style.WARNING("Superuser account already exists, exiting") + ) + return + + password = options.get("password") or os.environ.get( + "OBJECTTYPE_SUPERUSER_PASSWORD" + ) + + if password or options["generate_password"]: + options["interactive"] = False + + # perform user creation from core Django + super().handle(**options) + + user = qs.get() + + if not password and options["generate_password"]: + password = self.UserModel.objects.make_random_password(length=20) + + if password: + self.stdout.write("Setting user password...") + user.set_password(password) + user.save() + + if options["generate_password"]: + try: + link = f'{settings.ALLOWED_HOSTS[0]}{reverse("admin:index")}' + except IndexError: + link = "unknown url" + + send_mail( + f"Credentials for {settings.PROJECT_NAME} ({link})", + f"Credentials for project: {settings.PROJECT_NAME}\n\nUsername: {username}\nPassword: {password}", + settings.DEFAULT_FROM_EMAIL, + [user.email], + fail_silently=False, + ) diff --git a/src/objecttypes/accounts/tests/test_createinitialsuperuser.py b/src/objecttypes/accounts/tests/test_createinitialsuperuser.py new file mode 100644 index 00000000..fe208e09 --- /dev/null +++ b/src/objecttypes/accounts/tests/test_createinitialsuperuser.py @@ -0,0 +1,146 @@ +import io +import os + +from django.conf import settings +from django.core import mail +from django.core.management import call_command +from django.test import TestCase, override_settings +from django.urls import exceptions, reverse + +from ..models import User + + +class CreateInitialSuperuserTests(TestCase): + def test_create_initial_superuser_command(self): + call_command( + "createinitialsuperuser", + username="maykin", + email="support@maykinmedia.nl", + generate_password=True, + stdout=io.StringIO(), + stderr=io.StringIO(), + ) + user = User.objects.get() + + self.assertTrue(user.has_usable_password()) + self.assertTrue(user.is_active) + self.assertTrue(user.is_staff) + self.assertTrue(user.is_superuser) + + self.assertEqual(len(mail.outbox), 1) + + sent_mail = mail.outbox[0] + try: + link = f'{settings.ALLOWED_HOSTS[0]}{reverse("admin:index")}' + except exceptions.NoReverseMatch: + link = settings.ALLOWED_HOSTS[0] + self.assertEqual( + sent_mail.subject, f"Credentials for {settings.PROJECT_NAME} ({link})" + ) + self.assertListEqual(sent_mail.recipients(), ["support@maykinmedia.nl"]) + + @override_settings(ALLOWED_HOSTS=[]) + def test_create_initial_superuser_command_allowed_hosts_empty(self): + call_command( + "createinitialsuperuser", + username="maykin", + email="support@maykinmedia.nl", + generate_password=True, + stdout=io.StringIO(), + stderr=io.StringIO(), + ) + user = User.objects.get() + + self.assertTrue(user.has_usable_password()) + self.assertTrue(user.is_active) + self.assertTrue(user.is_staff) + self.assertTrue(user.is_superuser) + + self.assertEqual(len(mail.outbox), 1) + + sent_mail = mail.outbox[0] + link = "unknown url" + self.assertEqual( + sent_mail.subject, f"Credentials for {settings.PROJECT_NAME} ({link})" + ) + self.assertListEqual(sent_mail.recipients(), ["support@maykinmedia.nl"]) + + def test_create_from_cli(self): + call_command( + "createinitialsuperuser", + "--username=admin", + "--password=admin", + "--email=admin@example.com", + "--no-input", + stdout=io.StringIO(), + ) + + user = User.objects.get() + self.assertTrue(user.is_staff) + self.assertTrue(user.is_superuser) + + self.assertEqual(user.username, "admin") + self.assertEqual(user.email, "admin@example.com") + self.assertTrue(user.check_password("admin")) + + def test_command_noop_if_user_exists(self): + User.objects.create(username="admin") + + call_command( + "createinitialsuperuser", + "--username=admin", + "--password=admin", + "--email=admin@example.com", + "--no-input", + stdout=io.StringIO(), + ) + + self.assertEqual(User.objects.count(), 1) + user = User.objects.get() + self.assertFalse(user.is_staff) + self.assertFalse(user.is_superuser) + + self.assertEqual(user.username, "admin") + self.assertEqual(user.email, "") + self.assertFalse(user.check_password("admin")) + + def test_password_from_env(self): + os.environ["OBJECTTYPE_SUPERUSER_PASSWORD"] = "admin" + + def reset_env(): + del os.environ["OBJECTTYPE_SUPERUSER_PASSWORD"] + + self.addCleanup(reset_env) + + call_command( + "createinitialsuperuser", + "--username=admin", + "--email=admin@example.com", + "--no-input", + stdout=io.StringIO(), + ) + + user = User.objects.get() + self.assertTrue(user.is_staff) + self.assertTrue(user.is_superuser) + + self.assertEqual(user.username, "admin") + self.assertEqual(user.email, "admin@example.com") + self.assertTrue(user.check_password("admin")) + + def test_without_password(self): + call_command( + "createinitialsuperuser", + "--username=admin", + "--email=admin@example.com", + "--no-input", + stdout=io.StringIO(), + ) + + user = User.objects.get() + self.assertTrue(user.is_staff) + self.assertTrue(user.is_superuser) + + self.assertEqual(user.username, "admin") + self.assertEqual(user.email, "admin@example.com") + self.assertFalse(user.check_password("admin"))