diff --git a/Makefile b/Makefile index 54b33f235cb..84af57cc999 100644 --- a/Makefile +++ b/Makefile @@ -180,6 +180,7 @@ preflight: ${MAKE} install-local-python-deps $ npm install $ bin/sync-all.sh + $ python manage.py bootstrap_local_admin run-local-task-queue: # We temporarily source the .env for the command's duration only diff --git a/bedrock/cms/management/commands/bootstrap_local_admin.py b/bedrock/cms/management/commands/bootstrap_local_admin.py new file mode 100644 index 00000000000..1221d795396 --- /dev/null +++ b/bedrock/cms/management/commands/bootstrap_local_admin.py @@ -0,0 +1,46 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at https://mozilla.org/MPL/2.0/. + + +import sys + +from django.contrib.auth.models import User +from django.core.management.base import BaseCommand +from django.db.transaction import atomic + +from bedrock.base.config_manager import config + + +class Command(BaseCommand): + help = """Creates a Django/Wagtail Admin User based on the Mozilla + email address set as the WAGTAIL_ADMIN_EMAIL environment variable. + Optionally also sets a non-SSO password for that new user based on + WAGTAIL_ADMIN_PASSWORD""" + + @atomic + def handle(self, *args, **kwargs): + WAGTAIL_ADMIN_EMAIL = config("WAGTAIL_ADMIN_EMAIL", default="") + WAGTAIL_ADMIN_PASSWORD = config("WAGTAIL_ADMIN_PASSWORD", default="") + + if not WAGTAIL_ADMIN_EMAIL: + sys.stdout.write("Not bootstrapping an Admin user: WAGTAIL_ADMIN_EMAIL not defined in environment.") + return + if not WAGTAIL_ADMIN_EMAIL.endswith("@mozilla.com"): + sys.stdout.write("Not bootstrapping an Admin user: WAGTAIL_ADMIN_EMAIL is not a @mozilla.com email address.") + return + + user, created = User.objects.get_or_create(email=WAGTAIL_ADMIN_EMAIL) + if not created: + sys.stdout.write(f"Admin user {WAGTAIL_ADMIN_EMAIL} already exists") + else: + user.username = WAGTAIL_ADMIN_EMAIL + user.is_staff = True + user.is_superuser = True + if not WAGTAIL_ADMIN_PASSWORD: + user.set_unusable_password() # They won't need one to use SSO + sys.stdout.write(f"Created Admin user {WAGTAIL_ADMIN_EMAIL} for local SSO use") + else: + user.set_password(WAGTAIL_ADMIN_PASSWORD) + sys.stdout.write(f"Created Admin user {WAGTAIL_ADMIN_EMAIL} with password '{WAGTAIL_ADMIN_PASSWORD}'") + user.save() diff --git a/bedrock/cms/tests/test_commands.py b/bedrock/cms/tests/test_commands.py index 63dc4d200e6..ab91f1ffcbe 100644 --- a/bedrock/cms/tests/test_commands.py +++ b/bedrock/cms/tests/test_commands.py @@ -3,7 +3,7 @@ # file, You can obtain one at https://mozilla.org/MPL/2.0/. from io import StringIO -from unittest.mock import patch +from unittest.mock import call, patch from django.conf import settings from django.contrib.auth.models import User @@ -87,3 +87,81 @@ def test_scrub(self): self.assertEqual(Revision.objects.count(), 0) self.assertEqual(TranslationSource.objects.count(), 0) self.assertEqual(SimpleRichTextPage.objects.count(), 3) # pages are unaffected + + +@patch("bedrock.cms.management.commands.bootstrap_local_admin.sys.stdout.write") +class BootstrapLocalAdminTests(TransactionTestCase): + def _run_test(self, mock_write, expected_output): + out = StringIO() + call_command("bootstrap_local_admin", stdout=out) + output = mock_write.call_args_list + self.assertEqual(output, expected_output) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "", "WAGTAIL_ADMIN_PASSWORD": ""}) + def test_no_env_vars_available(self, mock_write): + self._run_test( + mock_write=mock_write, + expected_output=[ + call("Not bootstrapping an Admin user: WAGTAIL_ADMIN_EMAIL not defined in environment."), + ], + ) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "test@mozilla.com", "WAGTAIL_ADMIN_PASSWORD": ""}) + def test_email_available(self, mock_write): + self._run_test( + mock_write=mock_write, + expected_output=[ + call("Created Admin user test@mozilla.com for local SSO use"), + ], + ) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "test@example.com", "WAGTAIL_ADMIN_PASSWORD": ""}) + def test_email_available_but_not_moco(self, mock_write): + self._run_test( + mock_write=mock_write, + expected_output=[ + call("Not bootstrapping an Admin user: WAGTAIL_ADMIN_EMAIL is not a @mozilla.com email address."), + ], + ) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "test@mozilla.com", "WAGTAIL_ADMIN_PASSWORD": "secret"}) + def test_email_and_password_available(self, mock_write): + self._run_test( + mock_write=mock_write, + expected_output=[ + call("Created Admin user test@mozilla.com with password 'secret'"), + ], + ) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "", "WAGTAIL_ADMIN_PASSWORD": ""}) + def test_only_password_available(self, mock_write): + self._run_test( + mock_write=mock_write, + expected_output=[ + call("Not bootstrapping an Admin user: WAGTAIL_ADMIN_EMAIL not defined in environment."), + ], + ) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "test@mozilla.com", "WAGTAIL_ADMIN_PASSWORD": ""}) + def test_existing_user_exists_email_only(self, mock_write): + out = StringIO() + call_command("bootstrap_local_admin", stdout=out) + call_command("bootstrap_local_admin", stdout=out) + output = mock_write.call_args_list + expected_output = [ + call("Created Admin user test@mozilla.com for local SSO use"), + call("Admin user test@mozilla.com already exists"), + ] + self.assertEqual(output, expected_output) + + @patch.dict("os.environ", {"WAGTAIL_ADMIN_EMAIL": "test@mozilla.com", "WAGTAIL_ADMIN_PASSWORD": "secret"}) + def test_existing_user_exists_email_and_password(self, mock_write): + out = StringIO() + call_command("bootstrap_local_admin", stdout=out) + call_command("bootstrap_local_admin", stdout=out) + output = mock_write.call_args_list + expected_output = [ + call("Created Admin user test@mozilla.com with password 'secret'"), + call("Admin user test@mozilla.com already exists"), + ] + self.assertEqual(output, expected_output)