test_api.py 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406
  1. from django.test import override_settings
  2. from django.urls import reverse
  3. from core.models import ObjectType
  4. from users.models import Group, ObjectPermission, Token, User
  5. from utilities.data import deepmerge
  6. from utilities.testing import APIViewTestCases, APITestCase, create_test_user
  7. class AppTest(APITestCase):
  8. def test_root(self):
  9. url = reverse('users-api:api-root')
  10. response = self.client.get(f'{url}?format=api', **self.header)
  11. self.assertEqual(response.status_code, 200)
  12. class UserTest(APIViewTestCases.APIViewTestCase):
  13. model = User
  14. brief_fields = ['display', 'id', 'url', 'username']
  15. validation_excluded_fields = ['password']
  16. bulk_update_data = {
  17. 'email': 'test@example.com',
  18. }
  19. @classmethod
  20. def setUpTestData(cls):
  21. permissions = (
  22. ObjectPermission(name='Permission 1', actions=['view']),
  23. ObjectPermission(name='Permission 2', actions=['view']),
  24. ObjectPermission(name='Permission 3', actions=['view']),
  25. )
  26. ObjectPermission.objects.bulk_create(permissions)
  27. permissions[0].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'site'))
  28. permissions[1].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'location'))
  29. permissions[2].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'rack'))
  30. users = (
  31. User(username='User1', password='password1'),
  32. User(username='User2', password='password2'),
  33. User(username='User3', password='password3'),
  34. )
  35. User.objects.bulk_create(users)
  36. cls.create_data = [
  37. {
  38. 'username': 'User4',
  39. 'password': 'password4',
  40. 'permissions': [permissions[0].pk],
  41. },
  42. {
  43. 'username': 'User5',
  44. 'password': 'password5',
  45. 'permissions': [permissions[1].pk],
  46. },
  47. {
  48. 'username': 'User6',
  49. 'password': 'password6',
  50. 'permissions': [permissions[2].pk],
  51. },
  52. ]
  53. def test_that_password_is_changed(self):
  54. """
  55. Test that password is changed
  56. """
  57. obj_perm = ObjectPermission(
  58. name='Test permission',
  59. actions=['change']
  60. )
  61. obj_perm.save()
  62. obj_perm.users.add(self.user)
  63. obj_perm.object_types.add(ObjectType.objects.get_for_model(self.model))
  64. user_credentials = {
  65. 'username': 'newuser',
  66. 'password': 'abc123',
  67. }
  68. user = User.objects.create_user(**user_credentials)
  69. data = {
  70. 'password': 'newpassword'
  71. }
  72. url = reverse('users-api:user-detail', kwargs={'pk': user.id})
  73. response = self.client.patch(url, data, format='json', **self.header)
  74. self.assertEqual(response.status_code, 200)
  75. user.refresh_from_db()
  76. self.assertTrue(user.check_password(data['password']))
  77. @override_settings(AUTH_PASSWORD_VALIDATORS=[{
  78. 'NAME': 'django.contrib.auth.password_validation.MinimumLengthValidator',
  79. 'OPTIONS': {'min_length': 8}
  80. }])
  81. def test_password_validation_enforced(self):
  82. """
  83. Test that any configured password validation rules (AUTH_PASSWORD_VALIDATORS) are enforced.
  84. """
  85. self.add_permissions('users.add_user')
  86. data = {
  87. 'username': 'new_user',
  88. 'password': 'foo',
  89. }
  90. url = reverse('users-api:user-list')
  91. # Password too short
  92. response = self.client.post(url, data, format='json', **self.header)
  93. self.assertEqual(response.status_code, 400)
  94. # Password long enough
  95. data['password'] = 'foobar123'
  96. response = self.client.post(url, data, format='json', **self.header)
  97. self.assertEqual(response.status_code, 201)
  98. class GroupTest(APIViewTestCases.APIViewTestCase):
  99. model = Group
  100. brief_fields = ['description', 'display', 'id', 'name', 'url']
  101. @classmethod
  102. def setUpTestData(cls):
  103. permissions = (
  104. ObjectPermission(name='Permission 1', actions=['view']),
  105. ObjectPermission(name='Permission 2', actions=['view']),
  106. ObjectPermission(name='Permission 3', actions=['view']),
  107. )
  108. ObjectPermission.objects.bulk_create(permissions)
  109. permissions[0].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'site'))
  110. permissions[1].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'location'))
  111. permissions[2].object_types.add(ObjectType.objects.get_by_natural_key('dcim', 'rack'))
  112. groups = (
  113. Group(name='Group 1'),
  114. Group(name='Group 2'),
  115. Group(name='Group 3'),
  116. )
  117. Group.objects.bulk_create(groups)
  118. cls.create_data = [
  119. {
  120. 'name': 'Group 4',
  121. 'permissions': [permissions[0].pk],
  122. },
  123. {
  124. 'name': 'Group 5',
  125. 'permissions': [permissions[1].pk],
  126. },
  127. {
  128. 'name': 'Group 6',
  129. 'permissions': [permissions[2].pk],
  130. },
  131. ]
  132. def model_to_dict(self, instance, *args, **kwargs):
  133. # Overwrite permissions attr to work around the serializer field having a different name
  134. data = super().model_to_dict(instance, *args, **kwargs)
  135. data['permissions'] = list(instance.object_permissions.values_list('id', flat=True))
  136. return data
  137. def test_bulk_update_objects(self):
  138. """
  139. Disabled test. There's no attribute we can set in bulk for Groups.
  140. """
  141. return
  142. class TokenTest(
  143. # No GraphQL support for Token
  144. APIViewTestCases.GetObjectViewTestCase,
  145. APIViewTestCases.ListObjectsViewTestCase,
  146. APIViewTestCases.CreateObjectViewTestCase,
  147. APIViewTestCases.UpdateObjectViewTestCase,
  148. APIViewTestCases.DeleteObjectViewTestCase
  149. ):
  150. model = Token
  151. brief_fields = ['description', 'display', 'id', 'key', 'url', 'write_enabled']
  152. bulk_update_data = {
  153. 'description': 'New description',
  154. }
  155. def setUp(self):
  156. super().setUp()
  157. # Apply grant_token permission to enable the creation of Tokens for other Users
  158. self.add_permissions('users.grant_token')
  159. @classmethod
  160. def setUpTestData(cls):
  161. users = (
  162. create_test_user('User1'),
  163. create_test_user('User2'),
  164. create_test_user('User3'),
  165. )
  166. tokens = (
  167. Token(user=users[0]),
  168. Token(user=users[1]),
  169. Token(user=users[2]),
  170. )
  171. # Use save() instead of bulk_create() to ensure keys get automatically generated
  172. for token in tokens:
  173. token.save()
  174. cls.create_data = [
  175. {
  176. 'user': users[0].pk,
  177. },
  178. {
  179. 'user': users[1].pk,
  180. },
  181. {
  182. 'user': users[2].pk,
  183. },
  184. ]
  185. def test_provision_token_valid(self):
  186. """
  187. Test the provisioning of a new REST API token given a valid username and password.
  188. """
  189. user_credentials = {
  190. 'username': 'user1',
  191. 'password': 'abc123',
  192. }
  193. user = User.objects.create_user(**user_credentials)
  194. data = {
  195. **user_credentials,
  196. 'description': 'My API token',
  197. 'expires': '2099-12-31T23:59:59Z',
  198. }
  199. url = reverse('users-api:token_provision')
  200. response = self.client.post(url, data, format='json', **self.header)
  201. self.assertEqual(response.status_code, 201)
  202. self.assertIn('key', response.data)
  203. self.assertEqual(len(response.data['key']), 40)
  204. self.assertEqual(response.data['description'], data['description'])
  205. self.assertEqual(response.data['expires'], data['expires'])
  206. token = Token.objects.get(user=user)
  207. self.assertEqual(token.key, response.data['key'])
  208. def test_provision_token_invalid(self):
  209. """
  210. Test the behavior of the token provisioning view when invalid credentials are supplied.
  211. """
  212. data = {
  213. 'username': 'nonexistentuser',
  214. 'password': 'abc123',
  215. }
  216. url = reverse('users-api:token_provision')
  217. response = self.client.post(url, data, format='json', **self.header)
  218. self.assertEqual(response.status_code, 403)
  219. def test_provision_token_other_user(self):
  220. """
  221. Test provisioning a Token for a different User with & without the grant_token permission.
  222. """
  223. # Clear grant_token permission assigned by setUpTestData
  224. ObjectPermission.objects.filter(users=self.user).delete()
  225. self.add_permissions('users.add_token')
  226. user2 = User.objects.create_user(username='testuser2')
  227. data = {
  228. 'user': user2.id,
  229. }
  230. url = reverse('users-api:token-list')
  231. # Attempt to create a new Token for User2 *without* the grant_token permission
  232. response = self.client.post(url, data, format='json', **self.header)
  233. self.assertEqual(response.status_code, 403)
  234. # Assign grant_token permission and successfully create a new Token for User2
  235. self.add_permissions('users.grant_token')
  236. response = self.client.post(url, data, format='json', **self.header)
  237. self.assertEqual(response.status_code, 201)
  238. class ObjectPermissionTest(
  239. # No GraphQL support for ObjectPermission
  240. APIViewTestCases.GetObjectViewTestCase,
  241. APIViewTestCases.ListObjectsViewTestCase,
  242. APIViewTestCases.CreateObjectViewTestCase,
  243. APIViewTestCases.UpdateObjectViewTestCase,
  244. APIViewTestCases.DeleteObjectViewTestCase
  245. ):
  246. model = ObjectPermission
  247. brief_fields = ['actions', 'description', 'display', 'enabled', 'id', 'name', 'object_types', 'url']
  248. @classmethod
  249. def setUpTestData(cls):
  250. groups = (
  251. Group(name='Group 1'),
  252. Group(name='Group 2'),
  253. Group(name='Group 3'),
  254. )
  255. Group.objects.bulk_create(groups)
  256. users = (
  257. User(username='User1', is_active=True),
  258. User(username='User2', is_active=True),
  259. User(username='User3', is_active=True),
  260. )
  261. User.objects.bulk_create(users)
  262. object_type = ObjectType.objects.get(app_label='dcim', model='device')
  263. for i in range(3):
  264. objectpermission = ObjectPermission(
  265. name=f'Permission {i + 1}',
  266. actions=['view', 'add', 'change', 'delete'],
  267. constraints={'name': f'TEST{i + 1}'}
  268. )
  269. objectpermission.save()
  270. objectpermission.object_types.add(object_type)
  271. objectpermission.groups.add(groups[i])
  272. objectpermission.users.add(users[i])
  273. cls.create_data = [
  274. {
  275. 'name': 'Permission 4',
  276. 'object_types': ['dcim.site'],
  277. 'groups': [groups[0].pk],
  278. 'users': [users[0].pk],
  279. 'actions': ['view', 'add', 'change', 'delete'],
  280. 'constraints': {'name': 'TEST4'},
  281. },
  282. {
  283. 'name': 'Permission 5',
  284. 'object_types': ['dcim.site'],
  285. 'groups': [groups[1].pk],
  286. 'users': [users[1].pk],
  287. 'actions': ['view', 'add', 'change', 'delete'],
  288. 'constraints': {'name': 'TEST5'},
  289. },
  290. {
  291. 'name': 'Permission 6',
  292. 'object_types': ['dcim.site'],
  293. 'groups': [groups[2].pk],
  294. 'users': [users[2].pk],
  295. 'actions': ['view', 'add', 'change', 'delete'],
  296. 'constraints': {'name': 'TEST6'},
  297. },
  298. ]
  299. cls.bulk_update_data = {
  300. 'description': 'New description',
  301. }
  302. class UserConfigTest(APITestCase):
  303. def test_get(self):
  304. """
  305. Retrieve user configuration via GET request.
  306. """
  307. userconfig = self.user.config
  308. url = reverse('users-api:userconfig-list')
  309. response = self.client.get(url, **self.header)
  310. self.assertEqual(response.data, {})
  311. data = {
  312. "a": 123,
  313. "b": 456,
  314. "c": 789,
  315. }
  316. userconfig.data = data
  317. userconfig.save()
  318. response = self.client.get(url, **self.header)
  319. self.assertEqual(response.data, data)
  320. def test_patch(self):
  321. """
  322. Set user config via PATCH requests.
  323. """
  324. userconfig = self.user.config
  325. url = reverse('users-api:userconfig-list')
  326. data = {
  327. "a": {
  328. "a1": "X",
  329. "a2": "Y",
  330. },
  331. "b": {
  332. "b1": "Z",
  333. }
  334. }
  335. response = self.client.patch(url, data=data, format='json', **self.header)
  336. self.assertDictEqual(response.data, data)
  337. userconfig.refresh_from_db()
  338. self.assertDictEqual(userconfig.data, data)
  339. update_data = {
  340. "c": 123
  341. }
  342. response = self.client.patch(url, data=update_data, format='json', **self.header)
  343. new_data = deepmerge(data, update_data)
  344. self.assertDictEqual(response.data, new_data)
  345. userconfig.refresh_from_db()
  346. self.assertDictEqual(userconfig.data, new_data)