django-esi 8.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (100) hide show
  1. django_esi-8.1.0.dist-info/METADATA +93 -0
  2. django_esi-8.1.0.dist-info/RECORD +100 -0
  3. django_esi-8.1.0.dist-info/WHEEL +4 -0
  4. django_esi-8.1.0.dist-info/licenses/LICENSE +674 -0
  5. esi/__init__.py +7 -0
  6. esi/admin.py +42 -0
  7. esi/aiopenapi3/client.py +79 -0
  8. esi/aiopenapi3/plugins.py +224 -0
  9. esi/app_settings.py +112 -0
  10. esi/apps.py +11 -0
  11. esi/checks.py +56 -0
  12. esi/clients.py +657 -0
  13. esi/decorators.py +271 -0
  14. esi/errors.py +22 -0
  15. esi/exceptions.py +51 -0
  16. esi/helpers.py +63 -0
  17. esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
  18. esi/locale/cs_CZ/LC_MESSAGES/django.po +53 -0
  19. esi/locale/de/LC_MESSAGES/django.mo +0 -0
  20. esi/locale/de/LC_MESSAGES/django.po +58 -0
  21. esi/locale/en/LC_MESSAGES/django.mo +0 -0
  22. esi/locale/en/LC_MESSAGES/django.po +54 -0
  23. esi/locale/es/LC_MESSAGES/django.mo +0 -0
  24. esi/locale/es/LC_MESSAGES/django.po +59 -0
  25. esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
  26. esi/locale/fr_FR/LC_MESSAGES/django.po +59 -0
  27. esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
  28. esi/locale/it_IT/LC_MESSAGES/django.po +59 -0
  29. esi/locale/ja/LC_MESSAGES/django.mo +0 -0
  30. esi/locale/ja/LC_MESSAGES/django.po +58 -0
  31. esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
  32. esi/locale/ko_KR/LC_MESSAGES/django.po +58 -0
  33. esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
  34. esi/locale/nl_NL/LC_MESSAGES/django.po +53 -0
  35. esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
  36. esi/locale/pl_PL/LC_MESSAGES/django.po +53 -0
  37. esi/locale/ru/LC_MESSAGES/django.mo +0 -0
  38. esi/locale/ru/LC_MESSAGES/django.po +61 -0
  39. esi/locale/sk/LC_MESSAGES/django.mo +0 -0
  40. esi/locale/sk/LC_MESSAGES/django.po +55 -0
  41. esi/locale/uk/LC_MESSAGES/django.mo +0 -0
  42. esi/locale/uk/LC_MESSAGES/django.po +57 -0
  43. esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
  44. esi/locale/zh_Hans/LC_MESSAGES/django.po +58 -0
  45. esi/management/commands/__init__.py +0 -0
  46. esi/management/commands/esi_clear_spec_cache.py +21 -0
  47. esi/management/commands/generate_esi_stubs.py +661 -0
  48. esi/management/commands/migrate_to_ssov2.py +188 -0
  49. esi/managers.py +303 -0
  50. esi/managers.pyi +85 -0
  51. esi/migrations/0001_initial.py +55 -0
  52. esi/migrations/0002_scopes_20161208.py +56 -0
  53. esi/migrations/0003_hide_tokens_from_admin_site.py +23 -0
  54. esi/migrations/0004_remove_unique_access_token.py +18 -0
  55. esi/migrations/0005_remove_token_length_limit.py +23 -0
  56. esi/migrations/0006_remove_url_length_limit.py +18 -0
  57. esi/migrations/0007_fix_mysql_8_migration.py +18 -0
  58. esi/migrations/0008_nullable_refresh_token.py +18 -0
  59. esi/migrations/0009_set_old_tokens_to_sso_v1.py +18 -0
  60. esi/migrations/0010_set_new_tokens_to_sso_v2.py +18 -0
  61. esi/migrations/0011_add_token_indices.py +28 -0
  62. esi/migrations/0012_fix_token_type_choices.py +18 -0
  63. esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
  64. esi/migrations/__init__.py +0 -0
  65. esi/models.py +349 -0
  66. esi/openapi_clients.py +1225 -0
  67. esi/rate_limiting.py +107 -0
  68. esi/signals.py +21 -0
  69. esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
  70. esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
  71. esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
  72. esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
  73. esi/stubs.py +2 -0
  74. esi/stubs.pyi +6807 -0
  75. esi/tasks.py +78 -0
  76. esi/templates/esi/select_token.html +116 -0
  77. esi/templatetags/__init__.py +0 -0
  78. esi/templatetags/scope_tags.py +8 -0
  79. esi/tests/__init__.py +134 -0
  80. esi/tests/client_authed_pilot.py +63 -0
  81. esi/tests/client_public_pilot.py +53 -0
  82. esi/tests/factories.py +47 -0
  83. esi/tests/factories_2.py +60 -0
  84. esi/tests/jwt_factory.py +135 -0
  85. esi/tests/test_checks.py +48 -0
  86. esi/tests/test_clients.py +1019 -0
  87. esi/tests/test_decorators.py +578 -0
  88. esi/tests/test_management_command.py +307 -0
  89. esi/tests/test_managers.py +673 -0
  90. esi/tests/test_models.py +403 -0
  91. esi/tests/test_openapi.json +854 -0
  92. esi/tests/test_openapi.py +1017 -0
  93. esi/tests/test_swagger.json +489 -0
  94. esi/tests/test_swagger_full.json +51112 -0
  95. esi/tests/test_tasks.py +116 -0
  96. esi/tests/test_templatetags.py +22 -0
  97. esi/tests/test_views.py +331 -0
  98. esi/tests/threading_pilot.py +69 -0
  99. esi/urls.py +9 -0
  100. esi/views.py +129 -0
@@ -0,0 +1,673 @@
1
+ from datetime import timedelta
2
+ from unittest.mock import Mock, patch
3
+
4
+ import requests_mock
5
+ from django.contrib.auth.models import User
6
+ from django.contrib.sessions.middleware import SessionMiddleware
7
+ from django.http import HttpResponse
8
+ from django.test import RequestFactory, TestCase
9
+ from django.utils.timezone import now
10
+
11
+ from ..errors import IncompleteResponseError, TokenError
12
+ from ..managers import _process_scopes
13
+ from ..models import Token
14
+ from . import _generate_token, _store_as_Token
15
+ from .jwt_factory import generate_jwk, generate_token
16
+
17
+
18
+ class TestProcessScopes(TestCase):
19
+ def test_none(self):
20
+ self.assertSetEqual(_process_scopes(None), set())
21
+
22
+ def test_empty_list(self):
23
+ self.assertSetEqual(_process_scopes([]), set())
24
+
25
+ def test_single_string_1(self):
26
+ self.assertSetEqual(_process_scopes(["one"]), {"one"})
27
+
28
+ def test_single_string_2(self):
29
+ self.assertSetEqual(_process_scopes(["one two three"]), {"one", "two", "three"})
30
+
31
+ def test_list(self):
32
+ self.assertSetEqual(
33
+ _process_scopes(["one", "two", "three"]), {"one", "two", "three"}
34
+ )
35
+
36
+ def test_tuple(self):
37
+ self.assertSetEqual(
38
+ _process_scopes(("one", "two", "three")), {"one", "two", "three"}
39
+ )
40
+
41
+
42
+ class TestTokenGetExpired(TestCase):
43
+ @patch("esi.models.app_settings.ESI_TOKEN_VALID_DURATION", 120)
44
+ def test_get_expired(self):
45
+ self.user1 = User.objects.create_user(
46
+ "Bruce Wayne", "abc@example.com", "password"
47
+ )
48
+ self.user2 = User.objects.create_user(
49
+ "Peter Parker", "abc@example.com", "password"
50
+ )
51
+ _store_as_Token(
52
+ _generate_token(
53
+ character_id=101, character_name=self.user1.username, scopes=["abc"]
54
+ ),
55
+ self.user1,
56
+ )
57
+ t2 = _store_as_Token(
58
+ _generate_token(
59
+ character_id=102, character_name=self.user2.username, scopes=["xyz"]
60
+ ),
61
+ self.user2,
62
+ )
63
+ self.assertEqual(list(Token.objects.get_queryset().get_expired()), [])
64
+
65
+ t2.created -= timedelta(121)
66
+ t2.save()
67
+ self.assertEqual(list(Token.objects.get_queryset().get_expired()), [t2])
68
+
69
+
70
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_ID", "abc")
71
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_SECRET", "xyz")
72
+ @patch("esi.models.Token.delete", autospec=True)
73
+ @patch("esi.models.Token.refresh", autospec=True)
74
+ @patch("esi.managers.requests.auth.HTTPBasicAuth", autospec=True)
75
+ @patch("esi.managers.OAuth2Session", autospec=True)
76
+ class TestTokenBulkRefresh(TestCase):
77
+ @classmethod
78
+ def setUpClass(cls) -> None:
79
+ super().setUpClass()
80
+ cls.user = User.objects.create_user(
81
+ "Bruce Wayne", "abc@example.com", "password"
82
+ )
83
+
84
+ def test_bulk_refresh_normal(
85
+ self,
86
+ mock_OAuth2Session,
87
+ mock_HTTPBasicAuth,
88
+ mock_Token_refresh,
89
+ mock_Token_delete,
90
+ ):
91
+ character_id = 99
92
+ character_name = "Bruce Wayne"
93
+ t1 = _store_as_Token(
94
+ _generate_token(
95
+ character_id=character_id, character_name=character_name, scopes=["abc"]
96
+ ),
97
+ self.user,
98
+ )
99
+ t2 = _store_as_Token(
100
+ _generate_token(
101
+ character_id=character_id, character_name=character_name, scopes=["xyz"]
102
+ ),
103
+ self.user,
104
+ )
105
+ incomplete_qs = Token.objects.get_queryset().bulk_refresh()
106
+ self.assertEqual(mock_Token_refresh.call_count, 2)
107
+ self.assertEqual(mock_Token_delete.call_count, 0)
108
+ self.assertSetEqual(set(incomplete_qs), {t1, t2})
109
+
110
+ # Note
111
+ # looks like a bug in bulk_refresh():
112
+ # this filter can never find anything, because refresh_token
113
+ # can not be null:
114
+ # self.filter(refresh_token__isnull=True).get_expired().delete()
115
+
116
+ def test_bulk_refresh_token_error(
117
+ self,
118
+ mock_OAuth2Session,
119
+ mock_HTTPBasicAuth,
120
+ mock_Token_refresh,
121
+ mock_Token_delete,
122
+ ):
123
+ mock_Token_refresh.side_effect = TokenError
124
+
125
+ character_id = 99
126
+ character_name = "Bruce Wayne"
127
+ t1 = _store_as_Token(
128
+ _generate_token(
129
+ character_id=character_id, character_name=character_name, scopes=["abc"]
130
+ ),
131
+ self.user,
132
+ )
133
+ t2 = _store_as_Token(
134
+ _generate_token(
135
+ character_id=character_id, character_name=character_name, scopes=["xyz"]
136
+ ),
137
+ self.user,
138
+ )
139
+ incomplete_qs = Token.objects.get_queryset().bulk_refresh()
140
+ self.assertEqual(mock_Token_refresh.call_count, 2)
141
+ self.assertEqual(mock_Token_delete.call_count, 2)
142
+ self.assertSetEqual(set(incomplete_qs), {t1, t2})
143
+
144
+ def test_bulk_refresh_incomplete_response_error(
145
+ self,
146
+ mock_OAuth2Session,
147
+ mock_HTTPBasicAuth,
148
+ mock_Token_refresh,
149
+ mock_Token_delete,
150
+ ):
151
+ mock_Token_refresh.side_effect = IncompleteResponseError
152
+
153
+ character_id = 99
154
+ character_name = "Bruce Wayne"
155
+ _store_as_Token(
156
+ _generate_token(
157
+ character_id=character_id, character_name=character_name, scopes=["abc"]
158
+ ),
159
+ self.user,
160
+ )
161
+ _store_as_Token(
162
+ _generate_token(
163
+ character_id=character_id, character_name=character_name, scopes=["xyz"]
164
+ ),
165
+ self.user,
166
+ )
167
+ incomplete_qs = Token.objects.get_queryset().bulk_refresh()
168
+ self.assertEqual(mock_Token_refresh.call_count, 2)
169
+ self.assertEqual(mock_Token_delete.call_count, 0)
170
+ self.assertSetEqual(set(incomplete_qs), set())
171
+
172
+
173
+ @patch("esi.models.app_settings.ESI_TOKEN_VALID_DURATION", 120)
174
+ @patch("esi.managers.TokenQueryset.bulk_refresh", autospec=True)
175
+ class TestTokenRequireValid(TestCase):
176
+ @classmethod
177
+ def setUpClass(cls) -> None:
178
+ super().setUpClass()
179
+ cls.user = User.objects.create_user(
180
+ "Bruce Wayne", "abc@example.com", "password"
181
+ )
182
+
183
+ def test_require_valid_none_expired(self, mock_bulk_refresh):
184
+ character_id = 99
185
+ character_name = "Bruce Wayne"
186
+ t1 = _store_as_Token(
187
+ _generate_token(
188
+ character_id=character_id, character_name=character_name, scopes=["abc"]
189
+ ),
190
+ self.user,
191
+ )
192
+ t2 = _store_as_Token(
193
+ _generate_token(
194
+ character_id=character_id, character_name=character_name, scopes=["xyz"]
195
+ ),
196
+ self.user,
197
+ )
198
+ mock_bulk_refresh.return_value = Token.objects.none()
199
+
200
+ self.assertSetEqual(set(Token.objects.get_queryset().require_valid()), {t1, t2})
201
+
202
+ def test_require_valid_some_expired(self, mock_bulk_refresh):
203
+ character_id = 99
204
+ character_name = "Bruce Wayne"
205
+ t1 = _store_as_Token(
206
+ _generate_token(
207
+ character_id=character_id, character_name=character_name, scopes=["abc"]
208
+ ),
209
+ self.user,
210
+ )
211
+ t2 = _store_as_Token(
212
+ _generate_token(
213
+ character_id=character_id, character_name=character_name, scopes=["xyz"]
214
+ ),
215
+ self.user,
216
+ )
217
+ t2.created -= timedelta(121)
218
+ t2.save()
219
+ mock_bulk_refresh.return_value = Token.objects.filter(pk__in=[t2.pk])
220
+
221
+ self.assertSetEqual(set(Token.objects.get_queryset().require_valid()), {t1, t2})
222
+
223
+ def test_require_valid_one_refresh_error(self, mock_bulk_refresh):
224
+ character_id = 99
225
+ character_name = "Bruce Wayne"
226
+ t1 = _store_as_Token(
227
+ _generate_token(
228
+ character_id=character_id, character_name=character_name, scopes=["abc"]
229
+ ),
230
+ self.user,
231
+ )
232
+ t2 = _store_as_Token(
233
+ _generate_token(
234
+ character_id=character_id, character_name=character_name, scopes=["xyz"]
235
+ ),
236
+ self.user,
237
+ )
238
+ t2.created -= timedelta(121)
239
+ t2.save()
240
+ mock_bulk_refresh.return_value = Token.objects.none()
241
+
242
+ self.assertSetEqual(set(Token.objects.get_queryset().require_valid()), {t1})
243
+
244
+
245
+ @patch("esi.models.app_settings.ESI_TOKEN_VALID_DURATION", 120)
246
+ @patch("esi.models.Token.refresh", spec=True)
247
+ class TestTokenBulkRefreshXRequireValid(TestCase):
248
+ @classmethod
249
+ def setUpClass(cls) -> None:
250
+ super().setUpClass()
251
+ cls.user = User.objects.create_user(
252
+ "Bruce Wayne", "abc@example.com", "password"
253
+ )
254
+
255
+ def test_require_valid_all_expired(self, mock_token_refresh):
256
+ """Test demonstrates the bug introduced with 5.3.0b1.
257
+
258
+ bulk_refresh() does not return the token after it has been refreshed.
259
+ Then also require_valid() does not return the refreshed token.
260
+ Apps calling require_valid() will then assume there is no valid token.
261
+ The previous tests did not catch this bug, because it they were testing
262
+ the methods one by one and mocking out responses from other methods.
263
+ """
264
+ def my_refresh(*args, **kwargs):
265
+ t.created = now()
266
+ t.save()
267
+
268
+ # given
269
+ mock_token_refresh.side_effect = my_refresh
270
+ character_id = 99
271
+ character_name = "Bruce Wayne"
272
+ t: Token = _store_as_Token(
273
+ _generate_token(
274
+ character_id=character_id, character_name=character_name, scopes=["abc"]
275
+ ),
276
+ self.user,
277
+ )
278
+ t.created -= timedelta(121)
279
+ t.save()
280
+
281
+ # when
282
+ qs = Token.objects.get_queryset().require_valid()
283
+
284
+ # then
285
+ expected = set(qs.values_list("pk", flat=True))
286
+ self.assertSetEqual(expected, {t.pk})
287
+
288
+
289
+ class TestTokenRequireScopes(TestCase):
290
+ @classmethod
291
+ def setUpClass(cls) -> None:
292
+ super().setUpClass()
293
+ cls.user = User.objects.create_user(
294
+ "Bruce Wayne", "abc@example.com", "password"
295
+ )
296
+
297
+ def test_require_scopes_normal(self):
298
+ character_id = 99
299
+ character_name = "Bruce Wayne"
300
+ t1 = _store_as_Token(
301
+ _generate_token(
302
+ character_id=character_id,
303
+ character_name=character_name,
304
+ scopes=["abc", "xyz", "123"],
305
+ ),
306
+ self.user,
307
+ )
308
+ t2 = _store_as_Token(
309
+ _generate_token(
310
+ character_id=character_id,
311
+ character_name=character_name,
312
+ scopes=["abc", "xyz"],
313
+ ),
314
+ self.user,
315
+ )
316
+ t3 = _store_as_Token(
317
+ _generate_token(
318
+ character_id=character_id,
319
+ character_name=character_name,
320
+ scopes=["abc", "123"],
321
+ ),
322
+ self.user,
323
+ )
324
+ self.assertSetEqual(
325
+ set(Token.objects.get_queryset().require_scopes("abc")), {t1, t2, t3}
326
+ )
327
+ self.assertSetEqual(
328
+ set(Token.objects.get_queryset().require_scopes("xyz")), {t1, t2}
329
+ )
330
+ self.assertSetEqual(
331
+ set(Token.objects.get_queryset().require_scopes("123")), {t1, t3}
332
+ )
333
+ self.assertSetEqual(
334
+ set(Token.objects.get_queryset().require_scopes("555")), set()
335
+ )
336
+ self.assertSetEqual(
337
+ set(Token.objects.get_queryset().require_scopes("abc xyz 123")), {t1}
338
+ )
339
+
340
+ def test_require_scopes_empty(self):
341
+ character_id = 99
342
+ character_name = "Bruce Wayne"
343
+ _store_as_Token(
344
+ _generate_token(
345
+ character_id=character_id,
346
+ character_name=character_name,
347
+ scopes=["abc", "xyz", "123"],
348
+ ),
349
+ self.user,
350
+ )
351
+ t2 = _store_as_Token(
352
+ _generate_token(character_id=character_id, character_name=character_name),
353
+ self.user,
354
+ )
355
+ t2.scopes.all().delete()
356
+ _store_as_Token(
357
+ _generate_token(
358
+ character_id=character_id,
359
+ character_name=character_name,
360
+ scopes=["abc", "123"],
361
+ ),
362
+ self.user,
363
+ )
364
+ self.assertSetEqual(set(Token.objects.get_queryset().require_scopes("")), {t2})
365
+
366
+ def test_require_scopes_exact(self):
367
+ character_id = 99
368
+ character_name = "Bruce Wayne"
369
+ _store_as_Token(
370
+ _generate_token(
371
+ character_id=character_id,
372
+ character_name=character_name,
373
+ scopes=["abc", "xyz", "123"],
374
+ ),
375
+ self.user,
376
+ )
377
+ t2 = _store_as_Token(
378
+ _generate_token(
379
+ character_id=character_id,
380
+ character_name=character_name,
381
+ scopes=["abc", "xyz"],
382
+ ),
383
+ self.user,
384
+ )
385
+ t3 = _store_as_Token(
386
+ _generate_token(
387
+ character_id=character_id,
388
+ character_name=character_name,
389
+ scopes=["abc", "123"],
390
+ ),
391
+ self.user,
392
+ )
393
+ t4 = _store_as_Token(
394
+ _generate_token(
395
+ character_id=character_id,
396
+ character_name=character_name,
397
+ scopes=["abc", "123"],
398
+ ),
399
+ self.user,
400
+ )
401
+ self.assertSetEqual(
402
+ set(Token.objects.get_queryset().require_scopes_exact("abc")), set()
403
+ )
404
+ self.assertSetEqual(
405
+ set(Token.objects.get_queryset().require_scopes_exact("abc xyz")), {t2}
406
+ )
407
+ self.assertSetEqual(
408
+ set(Token.objects.get_queryset().require_scopes_exact("abc 123")), {t3, t4}
409
+ )
410
+
411
+ def test_require_scopes_exact_2(self):
412
+ character_id = 99
413
+ character_name = "Bruce Wayne"
414
+ _store_as_Token(
415
+ _generate_token(
416
+ character_id=character_id,
417
+ character_name=character_name,
418
+ scopes=["abc", "xyz", "123"],
419
+ ),
420
+ self.user,
421
+ )
422
+ _store_as_Token(
423
+ _generate_token(
424
+ character_id=character_id,
425
+ character_name=character_name,
426
+ scopes=["abc", "xyz"],
427
+ ),
428
+ self.user,
429
+ )
430
+ t3 = _store_as_Token(
431
+ _generate_token(
432
+ character_id=character_id,
433
+ character_name=character_name,
434
+ scopes=["xyz", "123"],
435
+ ),
436
+ self.user,
437
+ )
438
+ t4 = _store_as_Token(
439
+ _generate_token(
440
+ character_id=character_id,
441
+ character_name=character_name,
442
+ scopes=["xyz", "123"],
443
+ ),
444
+ self.user,
445
+ )
446
+ self.assertSetEqual(set(Token.objects.get_queryset().equivalent_to(t3)), {t4})
447
+
448
+
449
+ class TestTokenManager(TestCase):
450
+
451
+ def setUp(self):
452
+ self.factory = RequestFactory()
453
+
454
+ self.user1 = User.objects.create_user(
455
+ "Bruce Wayne", "abc@example.com", "password"
456
+ )
457
+
458
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_ID", "abc")
459
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_SECRET", "xyz")
460
+ @patch("esi.managers.app_settings.ESI_SSO_CALLBACK_URL", "localhost")
461
+ @patch("esi.managers.app_settings.ESI_TOKEN_URL", "localhost")
462
+ @patch("esi.managers.app_settings.ESI_TOKEN_VERIFY_URL", "localhost")
463
+ @patch("esi.managers.app_settings.ESI_ALWAYS_CREATE_TOKEN", False)
464
+ @patch("esi.managers.TokenManager._decode_jwt")
465
+ @patch("esi.managers.OAuth2Session", autospec=True)
466
+ def test_create_from_code_single_scope(self, mock_OAuth2Session, mock_decode_jwt):
467
+ """Normal case with refresh token"""
468
+ mock_oauth = Mock()
469
+ mock_oauth.request.return_value.json.return_value = _generate_token(
470
+ 99, "Bruce Wayne", scopes="publicData"
471
+ )
472
+ mock_oauth.fetch_token.return_value = {
473
+ "access_token": "access_token",
474
+ "refresh_token": "refresh_token",
475
+ "token_type": "Bearer",
476
+ "expires_in": 1200,
477
+ }
478
+ mock_OAuth2Session.return_value = mock_oauth
479
+ mock_decode_jwt.return_value = _generate_token(
480
+ 99, "Bruce Wayne", scopes="publicData"
481
+ )
482
+ # create new token from code
483
+ token1 = Token.objects.create_from_code("abc123xyz")
484
+ self.assertEqual(token1.character_id, 99)
485
+ self.assertEqual(token1.character_name, "Bruce Wayne")
486
+ self.assertEqual(token1.scopes.all().count(), 1)
487
+ # should return existing token instead of creating a new one
488
+ # since ESI_ALWAYS_CREATE_TOKEN is False
489
+ token2 = Token.objects.create_from_code("11abc123xyz")
490
+ self.assertEqual(token1, token2)
491
+
492
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_ID", "abc")
493
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_SECRET", "xyz")
494
+ @patch("esi.managers.app_settings.ESI_SSO_CALLBACK_URL", "localhost")
495
+ @patch("esi.managers.app_settings.ESI_TOKEN_URL", "localhost")
496
+ @patch("esi.managers.app_settings.ESI_TOKEN_VERIFY_URL", "localhost")
497
+ @patch("esi.managers.app_settings.ESI_ALWAYS_CREATE_TOKEN", False)
498
+ @patch("esi.managers.TokenManager._decode_jwt")
499
+ @patch("esi.managers.OAuth2Session", autospec=True)
500
+ def test_create_from_code_1(self, mock_OAuth2Session, mock_decode_jwt):
501
+ """Normal case with refresh token"""
502
+ mock_oauth = Mock()
503
+ mock_oauth.request.return_value.json.return_value = _generate_token(
504
+ 99,
505
+ "Bruce Wayne",
506
+ scopes=[
507
+ "esi-calendar.read_calendar_events.v1",
508
+ "esi-location.read_location.v1",
509
+ "esi-location.read_ship_type.v1",
510
+ "esi-unknown-scope",
511
+ ],
512
+ )
513
+ mock_oauth.fetch_token.return_value = {
514
+ "access_token": "access_token",
515
+ "refresh_token": "refresh_token",
516
+ "token_type": "Bearer",
517
+ "expires_in": 1200,
518
+ }
519
+ mock_OAuth2Session.return_value = mock_oauth
520
+ mock_decode_jwt.return_value = _generate_token(
521
+ 99,
522
+ "Bruce Wayne",
523
+ scopes=[
524
+ "esi-calendar.read_calendar_events.v1",
525
+ "esi-location.read_location.v1",
526
+ "esi-location.read_ship_type.v1",
527
+ "esi-unknown-scope",
528
+ ],
529
+ )
530
+ # create new token from code
531
+ token1 = Token.objects.create_from_code("abc123xyz")
532
+ self.assertEqual(token1.character_id, 99)
533
+ self.assertEqual(token1.character_name, "Bruce Wayne")
534
+ self.assertEqual(token1.scopes.all().count(), 4)
535
+
536
+ # should return existing token instead of creating a new one
537
+ # since ESI_ALWAYS_CREATE_TOKEN is False
538
+ token2 = Token.objects.create_from_code("11abc123xyz")
539
+ self.assertEqual(token1, token2)
540
+
541
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_ID", "abc")
542
+ @patch("esi.managers.app_settings.ESI_SSO_CLIENT_SECRET", "xyz")
543
+ @patch("esi.managers.app_settings.ESI_SSO_CALLBACK_URL", "localhost")
544
+ @patch("esi.managers.app_settings.ESI_TOKEN_URL", "localhost")
545
+ @patch("esi.managers.app_settings.ESI_TOKEN_VERIFY_URL", "localhost")
546
+ @patch("esi.managers.app_settings.ESI_ALWAYS_CREATE_TOKEN", False)
547
+ @patch("esi.managers.TokenManager._decode_jwt")
548
+ @patch("esi.managers.OAuth2Session", autospec=True)
549
+ def test_create_from_code_2(self, mock_OAuth2Session, mock_decode_jwt):
550
+ """Special case w/o refresh token"""
551
+ mock_oauth = Mock()
552
+ mock_oauth.request.return_value.json.return_value = _generate_token(
553
+ 99,
554
+ "Bruce Wayne",
555
+ scopes=[
556
+ "esi-calendar.read_calendar_events.v1",
557
+ "esi-location.read_location.v1",
558
+ "esi-location.read_ship_type.v1",
559
+ "esi-unknown-scope",
560
+ ],
561
+ )
562
+ mock_oauth.fetch_token.return_value = {
563
+ "access_token": "access_token",
564
+ "refresh_token": None,
565
+ "token_type": "Bearer",
566
+ "expires_in": 1200,
567
+ }
568
+ mock_OAuth2Session.return_value = mock_oauth
569
+ mock_decode_jwt.return_value = _generate_token(
570
+ 99,
571
+ "Bruce Wayne",
572
+ scopes=[
573
+ "esi-calendar.read_calendar_events.v1",
574
+ "esi-location.read_location.v1",
575
+ "esi-location.read_ship_type.v1",
576
+ "esi-unknown-scope",
577
+ ],
578
+ )
579
+ # create new token from code
580
+ token1 = Token.objects.create_from_code("abc123xyz")
581
+ self.assertEqual(token1.character_id, 99)
582
+ self.assertEqual(token1.character_name, "Bruce Wayne")
583
+ self.assertEqual(token1.scopes.all().count(), 4)
584
+
585
+ # should return existing token instead of creating a new one
586
+ # since ESI_ALWAYS_CREATE_TOKEN is False
587
+ token2 = Token.objects.create_from_code("11abc123xyz")
588
+ self.assertEqual(token1, token2)
589
+
590
+ @patch("esi.managers.TokenManager.create_from_code", autospec=True)
591
+ def test_create_from_request(self, mock_create_from_code):
592
+ mock_create_from_code.return_value = "we got you"
593
+
594
+ request = self.factory.get("https://www.example.com?code=abc123")
595
+ request.user = self.user1
596
+
597
+ middleware = SessionMiddleware(HttpResponse)
598
+ middleware.process_request(request)
599
+ request.session.save()
600
+
601
+ x = Token.objects.create_from_request(request)
602
+ self.assertEqual(x, "we got you")
603
+ self.assertEqual(mock_create_from_code.call_args[0][1], "abc123")
604
+
605
+
606
+ @requests_mock.Mocker()
607
+ class TestTokenManagerValidateAccessToken(TestCase):
608
+ def test_should_return_token_1(self, requests_mocker):
609
+ # given
610
+ jwks = {"keys": [generate_jwk()]}
611
+ requests_mocker.register_uri(
612
+ "GET", url="https://login.eveonline.com/oauth/jwks", json=jwks
613
+ )
614
+ access_token, _ = generate_token(1001, "Bruce Wayne")
615
+ # when
616
+ token = Token.objects.validate_access_token(access_token)
617
+ # then
618
+ self.assertEqual(token["character_id"], 1001)
619
+ self.assertEqual(token["name"], "Bruce Wayne")
620
+ self.assertEqual(token["token_type"], "character")
621
+
622
+ def test_should_return_token_2(self, requests_mocker):
623
+ # given
624
+ jwks = {"keys": [generate_jwk()]}
625
+ requests_mocker.register_uri(
626
+ "GET", url="https://login.eveonline.com/oauth/jwks", json=jwks
627
+ )
628
+ access_token, _ = generate_token(
629
+ 1001, "Bruce Wayne", issuer="https://login.eveonline.com"
630
+ )
631
+ # when
632
+ token = Token.objects.validate_access_token(access_token)
633
+ # then
634
+ self.assertEqual(token["character_id"], 1001)
635
+ self.assertEqual(token["name"], "Bruce Wayne")
636
+ self.assertEqual(token["token_type"], "character")
637
+
638
+ def test_should_return_none_when_no_jwk(self, requests_mocker):
639
+ # given
640
+ jwks = dict()
641
+ requests_mocker.register_uri(
642
+ "GET", url="https://login.eveonline.com/oauth/jwks", json=jwks
643
+ )
644
+ access_token, _ = generate_token(1001, "Bruce Wayne", audience=False)
645
+ # when
646
+ result = Token.objects.validate_access_token(access_token)
647
+ # then
648
+ self.assertIsNone(result)
649
+
650
+ def test_should_return_none_when_expired(self, requests_mocker):
651
+ # given
652
+ jwks = {"keys": [generate_jwk()]}
653
+ requests_mocker.register_uri(
654
+ "GET", url="https://login.eveonline.com/oauth/jwks", json=jwks
655
+ )
656
+ issued_at = now() - timedelta(hours=3)
657
+ access_token, _ = generate_token(1001, "Bruce Wayne", issued_at=issued_at)
658
+ # when
659
+ result = Token.objects.validate_access_token(access_token)
660
+ # then
661
+ self.assertIsNone(result)
662
+
663
+ def test_should_return_none_when_invalid(self, requests_mocker):
664
+ # given
665
+ jwks = {"keys": [generate_jwk()]}
666
+ requests_mocker.register_uri(
667
+ "GET", url="https://login.eveonline.com/oauth/jwks", json=jwks
668
+ )
669
+ access_token = "invalid"
670
+ # when
671
+ result = Token.objects.validate_access_token(access_token)
672
+ # then
673
+ self.assertIsNone(result)