test_management.py 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508
  1. from __future__ import unicode_literals
  2. from datetime import date
  3. import locale
  4. import sys
  5. from django.apps import apps
  6. from django.contrib.auth import models, management
  7. from django.contrib.auth.checks import check_user_model
  8. from django.contrib.auth.management import create_permissions
  9. from django.contrib.auth.management.commands import changepassword, createsuperuser
  10. from django.contrib.auth.models import User
  11. from django.contrib.auth.tests.custom_user import CustomUser
  12. from django.contrib.auth.tests.utils import skipIfCustomUser
  13. from django.contrib.contenttypes.models import ContentType
  14. from django.core import checks
  15. from django.core import exceptions
  16. from django.core.management import call_command
  17. from django.core.management.base import CommandError
  18. from django.test import TestCase, override_settings, override_system_checks
  19. from django.utils import six
  20. from django.utils.encoding import force_str
  21. def mock_inputs(inputs):
  22. """
  23. Decorator to temporarily replace input/getpass to allow interactive
  24. createsuperuser.
  25. """
  26. def inner(test_func):
  27. def wrapped(*args):
  28. class mock_getpass:
  29. @staticmethod
  30. def getpass(prompt=b'Password: ', stream=None):
  31. if six.PY2:
  32. # getpass on Windows only supports prompt as bytestring (#19807)
  33. assert isinstance(prompt, six.binary_type)
  34. return inputs['password']
  35. def mock_input(prompt):
  36. # prompt should be encoded in Python 2. This line will raise an
  37. # Exception if prompt contains unencoded non-ASCII on Python 2.
  38. prompt = str(prompt)
  39. assert str('__proxy__') not in prompt
  40. response = ''
  41. for key, val in inputs.items():
  42. if force_str(key) in prompt.lower():
  43. response = val
  44. break
  45. return response
  46. old_getpass = createsuperuser.getpass
  47. old_input = createsuperuser.input
  48. createsuperuser.getpass = mock_getpass
  49. createsuperuser.input = mock_input
  50. try:
  51. test_func(*args)
  52. finally:
  53. createsuperuser.getpass = old_getpass
  54. createsuperuser.input = old_input
  55. return wrapped
  56. return inner
  57. class MockTTY(object):
  58. """
  59. A fake stdin object that pretends to be a TTY to be used in conjunction
  60. with mock_inputs.
  61. """
  62. def isatty(self):
  63. return True
  64. @skipIfCustomUser
  65. class GetDefaultUsernameTestCase(TestCase):
  66. def setUp(self):
  67. self.old_get_system_username = management.get_system_username
  68. def tearDown(self):
  69. management.get_system_username = self.old_get_system_username
  70. def test_actual_implementation(self):
  71. self.assertIsInstance(management.get_system_username(), six.text_type)
  72. def test_simple(self):
  73. management.get_system_username = lambda: 'joe'
  74. self.assertEqual(management.get_default_username(), 'joe')
  75. def test_existing(self):
  76. models.User.objects.create(username='joe')
  77. management.get_system_username = lambda: 'joe'
  78. self.assertEqual(management.get_default_username(), '')
  79. self.assertEqual(
  80. management.get_default_username(check_db=False), 'joe')
  81. def test_i18n(self):
  82. # 'Julia' with accented 'u':
  83. management.get_system_username = lambda: 'J\xfalia'
  84. self.assertEqual(management.get_default_username(), 'julia')
  85. @skipIfCustomUser
  86. class ChangepasswordManagementCommandTestCase(TestCase):
  87. def setUp(self):
  88. self.user = models.User.objects.create_user(username='joe', password='qwerty')
  89. self.stdout = six.StringIO()
  90. self.stderr = six.StringIO()
  91. def tearDown(self):
  92. self.stdout.close()
  93. self.stderr.close()
  94. def test_that_changepassword_command_changes_joes_password(self):
  95. "Executing the changepassword management command should change joe's password"
  96. self.assertTrue(self.user.check_password('qwerty'))
  97. command = changepassword.Command()
  98. command._get_pass = lambda *args: 'not qwerty'
  99. command.execute("joe", stdout=self.stdout)
  100. command_output = self.stdout.getvalue().strip()
  101. self.assertEqual(command_output, "Changing password for user 'joe'\nPassword changed successfully for user 'joe'")
  102. self.assertTrue(models.User.objects.get(username="joe").check_password("not qwerty"))
  103. def test_that_max_tries_exits_1(self):
  104. """
  105. A CommandError should be thrown by handle() if the user enters in
  106. mismatched passwords three times.
  107. """
  108. command = changepassword.Command()
  109. command._get_pass = lambda *args: args or 'foo'
  110. with self.assertRaises(CommandError):
  111. command.execute("joe", stdout=self.stdout, stderr=self.stderr)
  112. def test_that_changepassword_command_works_with_nonascii_output(self):
  113. """
  114. #21627 -- Executing the changepassword management command should allow
  115. non-ASCII characters from the User object representation.
  116. """
  117. # 'Julia' with accented 'u':
  118. models.User.objects.create_user(username='J\xfalia', password='qwerty')
  119. command = changepassword.Command()
  120. command._get_pass = lambda *args: 'not qwerty'
  121. command.execute("J\xfalia", stdout=self.stdout)
  122. @skipIfCustomUser
  123. class CreatesuperuserManagementCommandTestCase(TestCase):
  124. def test_basic_usage(self):
  125. "Check the operation of the createsuperuser management command"
  126. # We can use the management command to create a superuser
  127. new_io = six.StringIO()
  128. call_command(
  129. "createsuperuser",
  130. interactive=False,
  131. username="joe",
  132. email="joe@somewhere.org",
  133. stdout=new_io
  134. )
  135. command_output = new_io.getvalue().strip()
  136. self.assertEqual(command_output, 'Superuser created successfully.')
  137. u = User.objects.get(username="joe")
  138. self.assertEqual(u.email, 'joe@somewhere.org')
  139. # created password should be unusable
  140. self.assertFalse(u.has_usable_password())
  141. @mock_inputs({'password': "nopasswd"})
  142. def test_nolocale(self):
  143. """
  144. Check that createsuperuser does not break when no locale is set. See
  145. ticket #16017.
  146. """
  147. old_getdefaultlocale = locale.getdefaultlocale
  148. try:
  149. # Temporarily remove locale information
  150. locale.getdefaultlocale = lambda: (None, None)
  151. # Call the command in this new environment
  152. call_command(
  153. "createsuperuser",
  154. interactive=True,
  155. username="nolocale@somewhere.org",
  156. email="nolocale@somewhere.org",
  157. verbosity=0,
  158. stdin=MockTTY(),
  159. )
  160. except TypeError:
  161. self.fail("createsuperuser fails if the OS provides no information about the current locale")
  162. finally:
  163. # Re-apply locale information
  164. locale.getdefaultlocale = old_getdefaultlocale
  165. # If we were successful, a user should have been created
  166. u = User.objects.get(username="nolocale@somewhere.org")
  167. self.assertEqual(u.email, 'nolocale@somewhere.org')
  168. @mock_inputs({
  169. 'password': "nopasswd",
  170. 'u\u017eivatel': 'foo', # username (cz)
  171. 'email': 'nolocale@somewhere.org'})
  172. def test_non_ascii_verbose_name(self):
  173. # Aliased so the string doesn't get extracted
  174. from django.utils.translation import ugettext_lazy as ulazy
  175. username_field = User._meta.get_field('username')
  176. old_verbose_name = username_field.verbose_name
  177. username_field.verbose_name = ulazy('u\u017eivatel')
  178. new_io = six.StringIO()
  179. try:
  180. call_command(
  181. "createsuperuser",
  182. interactive=True,
  183. stdout=new_io,
  184. stdin=MockTTY(),
  185. )
  186. finally:
  187. username_field.verbose_name = old_verbose_name
  188. command_output = new_io.getvalue().strip()
  189. self.assertEqual(command_output, 'Superuser created successfully.')
  190. def test_verbosity_zero(self):
  191. # We can suppress output on the management command
  192. new_io = six.StringIO()
  193. call_command(
  194. "createsuperuser",
  195. interactive=False,
  196. username="joe2",
  197. email="joe2@somewhere.org",
  198. verbosity=0,
  199. stdout=new_io
  200. )
  201. command_output = new_io.getvalue().strip()
  202. self.assertEqual(command_output, '')
  203. u = User.objects.get(username="joe2")
  204. self.assertEqual(u.email, 'joe2@somewhere.org')
  205. self.assertFalse(u.has_usable_password())
  206. def test_email_in_username(self):
  207. new_io = six.StringIO()
  208. call_command(
  209. "createsuperuser",
  210. interactive=False,
  211. username="joe+admin@somewhere.org",
  212. email="joe@somewhere.org",
  213. stdout=new_io
  214. )
  215. u = User._default_manager.get(username="joe+admin@somewhere.org")
  216. self.assertEqual(u.email, 'joe@somewhere.org')
  217. self.assertFalse(u.has_usable_password())
  218. @override_settings(AUTH_USER_MODEL='auth.CustomUser')
  219. def test_swappable_user(self):
  220. "A superuser can be created when a custom User model is in use"
  221. # We can use the management command to create a superuser
  222. # We skip validation because the temporary substitution of the
  223. # swappable User model messes with validation.
  224. new_io = six.StringIO()
  225. call_command(
  226. "createsuperuser",
  227. interactive=False,
  228. email="joe@somewhere.org",
  229. date_of_birth="1976-04-01",
  230. stdout=new_io,
  231. skip_checks=True
  232. )
  233. command_output = new_io.getvalue().strip()
  234. self.assertEqual(command_output, 'Superuser created successfully.')
  235. u = CustomUser._default_manager.get(email="joe@somewhere.org")
  236. self.assertEqual(u.date_of_birth, date(1976, 4, 1))
  237. # created password should be unusable
  238. self.assertFalse(u.has_usable_password())
  239. @override_settings(AUTH_USER_MODEL='auth.CustomUser')
  240. def test_swappable_user_missing_required_field(self):
  241. "A Custom superuser won't be created when a required field isn't provided"
  242. # We can use the management command to create a superuser
  243. # We skip validation because the temporary substitution of the
  244. # swappable User model messes with validation.
  245. new_io = six.StringIO()
  246. with self.assertRaises(CommandError):
  247. call_command(
  248. "createsuperuser",
  249. interactive=False,
  250. username="joe@somewhere.org",
  251. stdout=new_io,
  252. stderr=new_io,
  253. skip_checks=True
  254. )
  255. self.assertEqual(CustomUser._default_manager.count(), 0)
  256. def test_skip_if_not_in_TTY(self):
  257. """
  258. If the command is not called from a TTY, it should be skipped and a
  259. message should be displayed (#7423).
  260. """
  261. class FakeStdin(object):
  262. """A fake stdin object that has isatty() return False."""
  263. def isatty(self):
  264. return False
  265. out = six.StringIO()
  266. call_command(
  267. "createsuperuser",
  268. stdin=FakeStdin(),
  269. stdout=out,
  270. interactive=True,
  271. )
  272. self.assertEqual(User._default_manager.count(), 0)
  273. self.assertIn("Superuser creation skipped", out.getvalue())
  274. def test_passing_stdin(self):
  275. """
  276. You can pass a stdin object as an option and it should be
  277. available on self.stdin.
  278. If no such option is passed, it defaults to sys.stdin.
  279. """
  280. sentinel = object()
  281. command = createsuperuser.Command()
  282. command.execute(
  283. stdin=sentinel,
  284. stdout=six.StringIO(),
  285. interactive=False,
  286. username='janet',
  287. email='janet@example.com',
  288. )
  289. self.assertIs(command.stdin, sentinel)
  290. command = createsuperuser.Command()
  291. command.execute(
  292. stdout=six.StringIO(),
  293. interactive=False,
  294. username='joe',
  295. email='joe@example.com',
  296. )
  297. self.assertIs(command.stdin, sys.stdin)
  298. class CustomUserModelValidationTestCase(TestCase):
  299. @override_settings(AUTH_USER_MODEL='auth.CustomUserNonListRequiredFields')
  300. @override_system_checks([check_user_model])
  301. def test_required_fields_is_list(self):
  302. "REQUIRED_FIELDS should be a list."
  303. from .custom_user import CustomUserNonListRequiredFields
  304. errors = checks.run_checks()
  305. expected = [
  306. checks.Error(
  307. "'REQUIRED_FIELDS' must be a list or tuple.",
  308. hint=None,
  309. obj=CustomUserNonListRequiredFields,
  310. id='auth.E001',
  311. ),
  312. ]
  313. self.assertEqual(errors, expected)
  314. @override_settings(AUTH_USER_MODEL='auth.CustomUserBadRequiredFields')
  315. @override_system_checks([check_user_model])
  316. def test_username_not_in_required_fields(self):
  317. "USERNAME_FIELD should not appear in REQUIRED_FIELDS."
  318. from .custom_user import CustomUserBadRequiredFields
  319. errors = checks.run_checks()
  320. expected = [
  321. checks.Error(
  322. ("The field named as the 'USERNAME_FIELD' for a custom user model "
  323. "must not be included in 'REQUIRED_FIELDS'."),
  324. hint=None,
  325. obj=CustomUserBadRequiredFields,
  326. id='auth.E002',
  327. ),
  328. ]
  329. self.assertEqual(errors, expected)
  330. @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername')
  331. @override_system_checks([check_user_model])
  332. def test_username_non_unique(self):
  333. "A non-unique USERNAME_FIELD should raise a model validation error."
  334. from .custom_user import CustomUserNonUniqueUsername
  335. errors = checks.run_checks()
  336. expected = [
  337. checks.Error(
  338. ("'CustomUserNonUniqueUsername.username' must be "
  339. "unique because it is named as the 'USERNAME_FIELD'."),
  340. hint=None,
  341. obj=CustomUserNonUniqueUsername,
  342. id='auth.E003',
  343. ),
  344. ]
  345. self.assertEqual(errors, expected)
  346. @override_settings(AUTH_USER_MODEL='auth.CustomUserNonUniqueUsername',
  347. AUTHENTICATION_BACKENDS=[
  348. 'my.custom.backend',
  349. ])
  350. @override_system_checks([check_user_model])
  351. def test_username_non_unique_with_custom_backend(self):
  352. """ A non-unique USERNAME_FIELD should raise an error only if we use the
  353. default authentication backend. Otherwise, an warning should be raised.
  354. """
  355. from .custom_user import CustomUserNonUniqueUsername
  356. errors = checks.run_checks()
  357. expected = [
  358. checks.Warning(
  359. ("'CustomUserNonUniqueUsername.username' is named as "
  360. "the 'USERNAME_FIELD', but it is not unique."),
  361. hint=('Ensure that your authentication backend(s) can handle '
  362. 'non-unique usernames.'),
  363. obj=CustomUserNonUniqueUsername,
  364. id='auth.W004',
  365. )
  366. ]
  367. self.assertEqual(errors, expected)
  368. class PermissionTestCase(TestCase):
  369. def setUp(self):
  370. self._original_permissions = models.Permission._meta.permissions[:]
  371. self._original_default_permissions = models.Permission._meta.default_permissions
  372. self._original_verbose_name = models.Permission._meta.verbose_name
  373. def tearDown(self):
  374. models.Permission._meta.permissions = self._original_permissions
  375. models.Permission._meta.default_permissions = self._original_default_permissions
  376. models.Permission._meta.verbose_name = self._original_verbose_name
  377. ContentType.objects.clear_cache()
  378. def test_duplicated_permissions(self):
  379. """
  380. Test that we show proper error message if we are trying to create
  381. duplicate permissions.
  382. """
  383. auth_app_config = apps.get_app_config('auth')
  384. # check duplicated default permission
  385. models.Permission._meta.permissions = [
  386. ('change_permission', 'Can edit permission (duplicate)')]
  387. six.assertRaisesRegex(self, CommandError,
  388. "The permission codename 'change_permission' clashes with a "
  389. "builtin permission for model 'auth.Permission'.",
  390. create_permissions, auth_app_config, verbosity=0)
  391. # check duplicated custom permissions
  392. models.Permission._meta.permissions = [
  393. ('my_custom_permission', 'Some permission'),
  394. ('other_one', 'Some other permission'),
  395. ('my_custom_permission', 'Some permission with duplicate permission code'),
  396. ]
  397. six.assertRaisesRegex(self, CommandError,
  398. "The permission codename 'my_custom_permission' is duplicated for model "
  399. "'auth.Permission'.",
  400. create_permissions, auth_app_config, verbosity=0)
  401. # should not raise anything
  402. models.Permission._meta.permissions = [
  403. ('my_custom_permission', 'Some permission'),
  404. ('other_one', 'Some other permission'),
  405. ]
  406. create_permissions(auth_app_config, verbosity=0)
  407. def test_default_permissions(self):
  408. auth_app_config = apps.get_app_config('auth')
  409. permission_content_type = ContentType.objects.get_by_natural_key('auth', 'permission')
  410. models.Permission._meta.permissions = [
  411. ('my_custom_permission', 'Some permission'),
  412. ]
  413. create_permissions(auth_app_config, verbosity=0)
  414. # add/change/delete permission by default + custom permission
  415. self.assertEqual(models.Permission.objects.filter(
  416. content_type=permission_content_type,
  417. ).count(), 4)
  418. models.Permission.objects.filter(content_type=permission_content_type).delete()
  419. models.Permission._meta.default_permissions = []
  420. create_permissions(auth_app_config, verbosity=0)
  421. # custom permission only since default permissions is empty
  422. self.assertEqual(models.Permission.objects.filter(
  423. content_type=permission_content_type,
  424. ).count(), 1)
  425. def test_verbose_name_length(self):
  426. auth_app_config = apps.get_app_config('auth')
  427. permission_content_type = ContentType.objects.get_by_natural_key('auth', 'permission')
  428. models.Permission.objects.filter(content_type=permission_content_type).delete()
  429. models.Permission._meta.verbose_name = "some ridiculously long verbose name that is out of control"
  430. six.assertRaisesRegex(self, exceptions.ValidationError,
  431. "The verbose_name of permission is longer than 39 characters",
  432. create_permissions, auth_app_config, verbosity=0)