test_management.py 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603
  1. import builtins
  2. import sys
  3. from datetime import date
  4. from io import StringIO
  5. from unittest import mock
  6. from django.apps import apps
  7. from django.contrib.auth import management
  8. from django.contrib.auth.management import create_permissions
  9. from django.contrib.auth.management.commands import (
  10. changepassword, createsuperuser,
  11. )
  12. from django.contrib.auth.models import Group, Permission, User
  13. from django.contrib.contenttypes.models import ContentType
  14. from django.core.management import call_command
  15. from django.core.management.base import CommandError
  16. from django.db import migrations
  17. from django.test import TestCase, override_settings
  18. from django.utils.translation import ugettext_lazy as _
  19. from .models import (
  20. CustomUser, CustomUserNonUniqueUsername, CustomUserWithFK, Email,
  21. )
  22. def mock_inputs(inputs):
  23. """
  24. Decorator to temporarily replace input/getpass to allow interactive
  25. createsuperuser.
  26. """
  27. def inner(test_func):
  28. def wrapped(*args):
  29. class mock_getpass:
  30. @staticmethod
  31. def getpass(prompt=b'Password: ', stream=None):
  32. if callable(inputs['password']):
  33. return inputs['password']()
  34. return inputs['password']
  35. def mock_input(prompt):
  36. assert '__proxy__' not in prompt
  37. response = ''
  38. for key, val in inputs.items():
  39. if key in prompt.lower():
  40. response = val
  41. break
  42. return response
  43. old_getpass = createsuperuser.getpass
  44. old_input = builtins.input
  45. createsuperuser.getpass = mock_getpass
  46. builtins.input = mock_input
  47. try:
  48. test_func(*args)
  49. finally:
  50. createsuperuser.getpass = old_getpass
  51. builtins.input = old_input
  52. return wrapped
  53. return inner
  54. class MockTTY:
  55. """
  56. A fake stdin object that pretends to be a TTY to be used in conjunction
  57. with mock_inputs.
  58. """
  59. def isatty(self):
  60. return True
  61. class GetDefaultUsernameTestCase(TestCase):
  62. def setUp(self):
  63. self.old_get_system_username = management.get_system_username
  64. def tearDown(self):
  65. management.get_system_username = self.old_get_system_username
  66. def test_actual_implementation(self):
  67. self.assertIsInstance(management.get_system_username(), str)
  68. def test_simple(self):
  69. management.get_system_username = lambda: 'joe'
  70. self.assertEqual(management.get_default_username(), 'joe')
  71. def test_existing(self):
  72. User.objects.create(username='joe')
  73. management.get_system_username = lambda: 'joe'
  74. self.assertEqual(management.get_default_username(), '')
  75. self.assertEqual(
  76. management.get_default_username(check_db=False), 'joe')
  77. def test_i18n(self):
  78. # 'Julia' with accented 'u':
  79. management.get_system_username = lambda: 'J\xfalia'
  80. self.assertEqual(management.get_default_username(), 'julia')
  81. @override_settings(AUTH_PASSWORD_VALIDATORS=[
  82. {'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'},
  83. ])
  84. class ChangepasswordManagementCommandTestCase(TestCase):
  85. def setUp(self):
  86. self.user = User.objects.create_user(username='joe', password='qwerty')
  87. self.stdout = StringIO()
  88. self.stderr = StringIO()
  89. def tearDown(self):
  90. self.stdout.close()
  91. self.stderr.close()
  92. @mock.patch.object(changepassword.Command, '_get_pass', return_value='not qwerty')
  93. def test_that_changepassword_command_changes_joes_password(self, mock_get_pass):
  94. "Executing the changepassword management command should change joe's password"
  95. self.assertTrue(self.user.check_password('qwerty'))
  96. call_command('changepassword', username='joe', stdout=self.stdout)
  97. command_output = self.stdout.getvalue().strip()
  98. self.assertEqual(
  99. command_output,
  100. "Changing password for user 'joe'\nPassword changed successfully for user 'joe'"
  101. )
  102. self.assertTrue(User.objects.get(username="joe").check_password("not qwerty"))
  103. @mock.patch.object(changepassword.Command, '_get_pass', side_effect=lambda *args: str(args))
  104. def test_that_max_tries_exits_1(self, mock_get_pass):
  105. """
  106. A CommandError should be thrown by handle() if the user enters in
  107. mismatched passwords three times.
  108. """
  109. with self.assertRaises(CommandError):
  110. call_command('changepassword', username='joe', stdout=self.stdout, stderr=self.stderr)
  111. @mock.patch.object(changepassword.Command, '_get_pass', return_value='1234567890')
  112. def test_password_validation(self, mock_get_pass):
  113. """
  114. A CommandError should be raised if the user enters in passwords which
  115. fail validation three times.
  116. """
  117. abort_msg = "Aborting password change for user 'joe' after 3 attempts"
  118. with self.assertRaisesMessage(CommandError, abort_msg):
  119. call_command('changepassword', username='joe', stdout=self.stdout, stderr=self.stderr)
  120. self.assertIn('This password is entirely numeric.', self.stderr.getvalue())
  121. @mock.patch.object(changepassword.Command, '_get_pass', return_value='not qwerty')
  122. def test_that_changepassword_command_works_with_nonascii_output(self, mock_get_pass):
  123. """
  124. #21627 -- Executing the changepassword management command should allow
  125. non-ASCII characters from the User object representation.
  126. """
  127. # 'Julia' with accented 'u':
  128. User.objects.create_user(username='J\xfalia', password='qwerty')
  129. call_command('changepassword', username='J\xfalia', stdout=self.stdout)
  130. class MultiDBChangepasswordManagementCommandTestCase(TestCase):
  131. multi_db = True
  132. @mock.patch.object(changepassword.Command, '_get_pass', return_value='not qwerty')
  133. def test_that_changepassword_command_with_database_option_uses_given_db(self, mock_get_pass):
  134. """
  135. changepassword --database should operate on the specified DB.
  136. """
  137. user = User.objects.db_manager('other').create_user(username='joe', password='qwerty')
  138. self.assertTrue(user.check_password('qwerty'))
  139. out = StringIO()
  140. call_command('changepassword', username='joe', database='other', stdout=out)
  141. command_output = out.getvalue().strip()
  142. self.assertEqual(
  143. command_output,
  144. "Changing password for user 'joe'\nPassword changed successfully for user 'joe'"
  145. )
  146. self.assertTrue(User.objects.using('other').get(username="joe").check_password('not qwerty'))
  147. @override_settings(
  148. SILENCED_SYSTEM_CHECKS=['fields.W342'], # ForeignKey(unique=True)
  149. AUTH_PASSWORD_VALIDATORS=[{'NAME': 'django.contrib.auth.password_validation.NumericPasswordValidator'}],
  150. )
  151. class CreatesuperuserManagementCommandTestCase(TestCase):
  152. def test_basic_usage(self):
  153. "Check the operation of the createsuperuser management command"
  154. # We can use the management command to create a superuser
  155. new_io = StringIO()
  156. call_command(
  157. "createsuperuser",
  158. interactive=False,
  159. username="joe",
  160. email="joe@somewhere.org",
  161. stdout=new_io
  162. )
  163. command_output = new_io.getvalue().strip()
  164. self.assertEqual(command_output, 'Superuser created successfully.')
  165. u = User.objects.get(username="joe")
  166. self.assertEqual(u.email, 'joe@somewhere.org')
  167. # created password should be unusable
  168. self.assertFalse(u.has_usable_password())
  169. @mock_inputs({
  170. 'password': "nopasswd",
  171. 'u\u017eivatel': 'foo', # username (cz)
  172. 'email': 'nolocale@somewhere.org'})
  173. def test_non_ascii_verbose_name(self):
  174. username_field = User._meta.get_field('username')
  175. old_verbose_name = username_field.verbose_name
  176. username_field.verbose_name = _('u\u017eivatel')
  177. new_io = StringIO()
  178. try:
  179. call_command(
  180. "createsuperuser",
  181. interactive=True,
  182. stdout=new_io,
  183. stdin=MockTTY(),
  184. )
  185. finally:
  186. username_field.verbose_name = old_verbose_name
  187. command_output = new_io.getvalue().strip()
  188. self.assertEqual(command_output, 'Superuser created successfully.')
  189. def test_verbosity_zero(self):
  190. # We can suppress output on the management command
  191. new_io = StringIO()
  192. call_command(
  193. "createsuperuser",
  194. interactive=False,
  195. username="joe2",
  196. email="joe2@somewhere.org",
  197. verbosity=0,
  198. stdout=new_io
  199. )
  200. command_output = new_io.getvalue().strip()
  201. self.assertEqual(command_output, '')
  202. u = User.objects.get(username="joe2")
  203. self.assertEqual(u.email, 'joe2@somewhere.org')
  204. self.assertFalse(u.has_usable_password())
  205. def test_email_in_username(self):
  206. new_io = StringIO()
  207. call_command(
  208. "createsuperuser",
  209. interactive=False,
  210. username="joe+admin@somewhere.org",
  211. email="joe@somewhere.org",
  212. stdout=new_io
  213. )
  214. u = User._default_manager.get(username="joe+admin@somewhere.org")
  215. self.assertEqual(u.email, 'joe@somewhere.org')
  216. self.assertFalse(u.has_usable_password())
  217. @override_settings(AUTH_USER_MODEL='auth_tests.CustomUser')
  218. def test_swappable_user(self):
  219. "A superuser can be created when a custom user model is in use"
  220. # We can use the management command to create a superuser
  221. # We skip validation because the temporary substitution of the
  222. # swappable User model messes with validation.
  223. new_io = StringIO()
  224. call_command(
  225. "createsuperuser",
  226. interactive=False,
  227. email="joe@somewhere.org",
  228. date_of_birth="1976-04-01",
  229. stdout=new_io,
  230. )
  231. command_output = new_io.getvalue().strip()
  232. self.assertEqual(command_output, 'Superuser created successfully.')
  233. u = CustomUser._default_manager.get(email="joe@somewhere.org")
  234. self.assertEqual(u.date_of_birth, date(1976, 4, 1))
  235. # created password should be unusable
  236. self.assertFalse(u.has_usable_password())
  237. @override_settings(AUTH_USER_MODEL='auth_tests.CustomUser')
  238. def test_swappable_user_missing_required_field(self):
  239. "A Custom superuser won't be created when a required field isn't provided"
  240. # We can use the management command to create a superuser
  241. # We skip validation because the temporary substitution of the
  242. # swappable User model messes with validation.
  243. new_io = StringIO()
  244. with self.assertRaises(CommandError):
  245. call_command(
  246. "createsuperuser",
  247. interactive=False,
  248. username="joe@somewhere.org",
  249. stdout=new_io,
  250. stderr=new_io,
  251. )
  252. self.assertEqual(CustomUser._default_manager.count(), 0)
  253. @override_settings(
  254. AUTH_USER_MODEL='auth_tests.CustomUserNonUniqueUsername',
  255. AUTHENTICATION_BACKENDS=['my.custom.backend'],
  256. )
  257. def test_swappable_user_username_non_unique(self):
  258. @mock_inputs({
  259. 'username': 'joe',
  260. 'password': 'nopasswd',
  261. })
  262. def createsuperuser():
  263. new_io = StringIO()
  264. call_command(
  265. "createsuperuser",
  266. interactive=True,
  267. email="joe@somewhere.org",
  268. stdout=new_io,
  269. stdin=MockTTY(),
  270. )
  271. command_output = new_io.getvalue().strip()
  272. self.assertEqual(command_output, 'Superuser created successfully.')
  273. for i in range(2):
  274. createsuperuser()
  275. users = CustomUserNonUniqueUsername.objects.filter(username="joe")
  276. self.assertEqual(users.count(), 2)
  277. def test_skip_if_not_in_TTY(self):
  278. """
  279. If the command is not called from a TTY, it should be skipped and a
  280. message should be displayed (#7423).
  281. """
  282. class FakeStdin:
  283. """A fake stdin object that has isatty() return False."""
  284. def isatty(self):
  285. return False
  286. out = StringIO()
  287. call_command(
  288. "createsuperuser",
  289. stdin=FakeStdin(),
  290. stdout=out,
  291. interactive=True,
  292. )
  293. self.assertEqual(User._default_manager.count(), 0)
  294. self.assertIn("Superuser creation skipped", out.getvalue())
  295. def test_passing_stdin(self):
  296. """
  297. You can pass a stdin object as an option and it should be
  298. available on self.stdin.
  299. If no such option is passed, it defaults to sys.stdin.
  300. """
  301. sentinel = object()
  302. command = createsuperuser.Command()
  303. call_command(
  304. command,
  305. stdin=sentinel,
  306. stdout=StringIO(),
  307. stderr=StringIO(),
  308. interactive=False,
  309. verbosity=0,
  310. username='janet',
  311. email='janet@example.com',
  312. )
  313. self.assertIs(command.stdin, sentinel)
  314. command = createsuperuser.Command()
  315. call_command(
  316. command,
  317. stdout=StringIO(),
  318. stderr=StringIO(),
  319. interactive=False,
  320. verbosity=0,
  321. username='joe',
  322. email='joe@example.com',
  323. )
  324. self.assertIs(command.stdin, sys.stdin)
  325. @override_settings(AUTH_USER_MODEL='auth_tests.CustomUserWithFK')
  326. def test_fields_with_fk(self):
  327. new_io = StringIO()
  328. group = Group.objects.create(name='mygroup')
  329. email = Email.objects.create(email='mymail@gmail.com')
  330. call_command(
  331. 'createsuperuser',
  332. interactive=False,
  333. username=email.pk,
  334. email=email.email,
  335. group=group.pk,
  336. stdout=new_io,
  337. )
  338. command_output = new_io.getvalue().strip()
  339. self.assertEqual(command_output, 'Superuser created successfully.')
  340. u = CustomUserWithFK._default_manager.get(email=email)
  341. self.assertEqual(u.username, email)
  342. self.assertEqual(u.group, group)
  343. non_existent_email = 'mymail2@gmail.com'
  344. msg = 'email instance with email %r does not exist.' % non_existent_email
  345. with self.assertRaisesMessage(CommandError, msg):
  346. call_command(
  347. 'createsuperuser',
  348. interactive=False,
  349. username=email.pk,
  350. email=non_existent_email,
  351. stdout=new_io,
  352. )
  353. @override_settings(AUTH_USER_MODEL='auth_tests.CustomUserWithFK')
  354. def test_fields_with_fk_interactive(self):
  355. new_io = StringIO()
  356. group = Group.objects.create(name='mygroup')
  357. email = Email.objects.create(email='mymail@gmail.com')
  358. @mock_inputs({
  359. 'password': 'nopasswd',
  360. 'username (email.id)': email.pk,
  361. 'email (email.email)': email.email,
  362. 'group (group.id)': group.pk,
  363. })
  364. def test(self):
  365. call_command(
  366. 'createsuperuser',
  367. interactive=True,
  368. stdout=new_io,
  369. stdin=MockTTY(),
  370. )
  371. command_output = new_io.getvalue().strip()
  372. self.assertEqual(command_output, 'Superuser created successfully.')
  373. u = CustomUserWithFK._default_manager.get(email=email)
  374. self.assertEqual(u.username, email)
  375. self.assertEqual(u.group, group)
  376. test(self)
  377. def test_password_validation(self):
  378. """
  379. Creation should fail if the password fails validation.
  380. """
  381. new_io = StringIO()
  382. # Returns '1234567890' the first two times it is called, then
  383. # 'password' subsequently.
  384. def bad_then_good_password(index=[0]):
  385. index[0] += 1
  386. if index[0] <= 2:
  387. return '1234567890'
  388. return 'password'
  389. @mock_inputs({
  390. 'password': bad_then_good_password,
  391. 'username': 'joe1234567890',
  392. })
  393. def test(self):
  394. call_command(
  395. "createsuperuser",
  396. interactive=True,
  397. stdin=MockTTY(),
  398. stdout=new_io,
  399. stderr=new_io,
  400. )
  401. self.assertEqual(
  402. new_io.getvalue().strip(),
  403. "This password is entirely numeric.\n"
  404. "Superuser created successfully."
  405. )
  406. test(self)
  407. def test_validation_mismatched_passwords(self):
  408. """
  409. Creation should fail if the user enters mismatched passwords.
  410. """
  411. new_io = StringIO()
  412. # The first two passwords do not match, but the second two do match and
  413. # are valid.
  414. entered_passwords = ["password", "not password", "password2", "password2"]
  415. def mismatched_passwords_then_matched():
  416. return entered_passwords.pop(0)
  417. @mock_inputs({
  418. 'password': mismatched_passwords_then_matched,
  419. 'username': 'joe1234567890',
  420. })
  421. def test(self):
  422. call_command(
  423. "createsuperuser",
  424. interactive=True,
  425. stdin=MockTTY(),
  426. stdout=new_io,
  427. stderr=new_io,
  428. )
  429. self.assertEqual(
  430. new_io.getvalue().strip(),
  431. "Error: Your passwords didn't match.\n"
  432. "Superuser created successfully."
  433. )
  434. test(self)
  435. def test_validation_blank_password_entered(self):
  436. """
  437. Creation should fail if the user enters blank passwords.
  438. """
  439. new_io = StringIO()
  440. # The first two passwords are empty strings, but the second two are
  441. # valid.
  442. entered_passwords = ["", "", "password2", "password2"]
  443. def blank_passwords_then_valid():
  444. return entered_passwords.pop(0)
  445. @mock_inputs({
  446. 'password': blank_passwords_then_valid,
  447. 'username': 'joe1234567890',
  448. })
  449. def test(self):
  450. call_command(
  451. "createsuperuser",
  452. interactive=True,
  453. stdin=MockTTY(),
  454. stdout=new_io,
  455. stderr=new_io,
  456. )
  457. self.assertEqual(
  458. new_io.getvalue().strip(),
  459. "Error: Blank passwords aren't allowed.\n"
  460. "Superuser created successfully."
  461. )
  462. test(self)
  463. class MultiDBCreatesuperuserTestCase(TestCase):
  464. multi_db = True
  465. def test_createsuperuser_command_with_database_option(self):
  466. """
  467. changepassword --database should operate on the specified DB.
  468. """
  469. new_io = StringIO()
  470. call_command(
  471. 'createsuperuser',
  472. interactive=False,
  473. username='joe',
  474. email='joe@somewhere.org',
  475. database='other',
  476. stdout=new_io,
  477. )
  478. command_output = new_io.getvalue().strip()
  479. self.assertEqual(command_output, 'Superuser created successfully.')
  480. user = User.objects.using('other').get(username='joe')
  481. self.assertEqual(user.email, 'joe@somewhere.org')
  482. class CreatePermissionsTests(TestCase):
  483. def setUp(self):
  484. self._original_permissions = Permission._meta.permissions[:]
  485. self._original_default_permissions = Permission._meta.default_permissions
  486. self.app_config = apps.get_app_config('auth')
  487. def tearDown(self):
  488. Permission._meta.permissions = self._original_permissions
  489. Permission._meta.default_permissions = self._original_default_permissions
  490. ContentType.objects.clear_cache()
  491. def test_default_permissions(self):
  492. permission_content_type = ContentType.objects.get_by_natural_key('auth', 'permission')
  493. Permission._meta.permissions = [
  494. ('my_custom_permission', 'Some permission'),
  495. ]
  496. create_permissions(self.app_config, verbosity=0)
  497. # add/change/delete permission by default + custom permission
  498. self.assertEqual(Permission.objects.filter(
  499. content_type=permission_content_type,
  500. ).count(), 4)
  501. Permission.objects.filter(content_type=permission_content_type).delete()
  502. Permission._meta.default_permissions = []
  503. create_permissions(self.app_config, verbosity=0)
  504. # custom permission only since default permissions is empty
  505. self.assertEqual(Permission.objects.filter(
  506. content_type=permission_content_type,
  507. ).count(), 1)
  508. def test_unavailable_models(self):
  509. """
  510. #24075 - Permissions shouldn't be created or deleted if the ContentType
  511. or Permission models aren't available.
  512. """
  513. state = migrations.state.ProjectState()
  514. # Unavailable contenttypes.ContentType
  515. with self.assertNumQueries(0):
  516. create_permissions(self.app_config, verbosity=0, apps=state.apps)
  517. # Unavailable auth.Permission
  518. state = migrations.state.ProjectState(real_apps=['contenttypes'])
  519. with self.assertNumQueries(0):
  520. create_permissions(self.app_config, verbosity=0, apps=state.apps)