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.
- django_esi-8.1.0.dist-info/METADATA +93 -0
- django_esi-8.1.0.dist-info/RECORD +100 -0
- django_esi-8.1.0.dist-info/WHEEL +4 -0
- django_esi-8.1.0.dist-info/licenses/LICENSE +674 -0
- esi/__init__.py +7 -0
- esi/admin.py +42 -0
- esi/aiopenapi3/client.py +79 -0
- esi/aiopenapi3/plugins.py +224 -0
- esi/app_settings.py +112 -0
- esi/apps.py +11 -0
- esi/checks.py +56 -0
- esi/clients.py +657 -0
- esi/decorators.py +271 -0
- esi/errors.py +22 -0
- esi/exceptions.py +51 -0
- esi/helpers.py +63 -0
- esi/locale/cs_CZ/LC_MESSAGES/django.mo +0 -0
- esi/locale/cs_CZ/LC_MESSAGES/django.po +53 -0
- esi/locale/de/LC_MESSAGES/django.mo +0 -0
- esi/locale/de/LC_MESSAGES/django.po +58 -0
- esi/locale/en/LC_MESSAGES/django.mo +0 -0
- esi/locale/en/LC_MESSAGES/django.po +54 -0
- esi/locale/es/LC_MESSAGES/django.mo +0 -0
- esi/locale/es/LC_MESSAGES/django.po +59 -0
- esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- esi/locale/fr_FR/LC_MESSAGES/django.po +59 -0
- esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- esi/locale/it_IT/LC_MESSAGES/django.po +59 -0
- esi/locale/ja/LC_MESSAGES/django.mo +0 -0
- esi/locale/ja/LC_MESSAGES/django.po +58 -0
- esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
- esi/locale/ko_KR/LC_MESSAGES/django.po +58 -0
- esi/locale/nl_NL/LC_MESSAGES/django.mo +0 -0
- esi/locale/nl_NL/LC_MESSAGES/django.po +53 -0
- esi/locale/pl_PL/LC_MESSAGES/django.mo +0 -0
- esi/locale/pl_PL/LC_MESSAGES/django.po +53 -0
- esi/locale/ru/LC_MESSAGES/django.mo +0 -0
- esi/locale/ru/LC_MESSAGES/django.po +61 -0
- esi/locale/sk/LC_MESSAGES/django.mo +0 -0
- esi/locale/sk/LC_MESSAGES/django.po +55 -0
- esi/locale/uk/LC_MESSAGES/django.mo +0 -0
- esi/locale/uk/LC_MESSAGES/django.po +57 -0
- esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- esi/locale/zh_Hans/LC_MESSAGES/django.po +58 -0
- esi/management/commands/__init__.py +0 -0
- esi/management/commands/esi_clear_spec_cache.py +21 -0
- esi/management/commands/generate_esi_stubs.py +661 -0
- esi/management/commands/migrate_to_ssov2.py +188 -0
- esi/managers.py +303 -0
- esi/managers.pyi +85 -0
- esi/migrations/0001_initial.py +55 -0
- esi/migrations/0002_scopes_20161208.py +56 -0
- esi/migrations/0003_hide_tokens_from_admin_site.py +23 -0
- esi/migrations/0004_remove_unique_access_token.py +18 -0
- esi/migrations/0005_remove_token_length_limit.py +23 -0
- esi/migrations/0006_remove_url_length_limit.py +18 -0
- esi/migrations/0007_fix_mysql_8_migration.py +18 -0
- esi/migrations/0008_nullable_refresh_token.py +18 -0
- esi/migrations/0009_set_old_tokens_to_sso_v1.py +18 -0
- esi/migrations/0010_set_new_tokens_to_sso_v2.py +18 -0
- esi/migrations/0011_add_token_indices.py +28 -0
- esi/migrations/0012_fix_token_type_choices.py +18 -0
- esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
- esi/migrations/__init__.py +0 -0
- esi/models.py +349 -0
- esi/openapi_clients.py +1225 -0
- esi/rate_limiting.py +107 -0
- esi/signals.py +21 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
- esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
- esi/stubs.py +2 -0
- esi/stubs.pyi +6807 -0
- esi/tasks.py +78 -0
- esi/templates/esi/select_token.html +116 -0
- esi/templatetags/__init__.py +0 -0
- esi/templatetags/scope_tags.py +8 -0
- esi/tests/__init__.py +134 -0
- esi/tests/client_authed_pilot.py +63 -0
- esi/tests/client_public_pilot.py +53 -0
- esi/tests/factories.py +47 -0
- esi/tests/factories_2.py +60 -0
- esi/tests/jwt_factory.py +135 -0
- esi/tests/test_checks.py +48 -0
- esi/tests/test_clients.py +1019 -0
- esi/tests/test_decorators.py +578 -0
- esi/tests/test_management_command.py +307 -0
- esi/tests/test_managers.py +673 -0
- esi/tests/test_models.py +403 -0
- esi/tests/test_openapi.json +854 -0
- esi/tests/test_openapi.py +1017 -0
- esi/tests/test_swagger.json +489 -0
- esi/tests/test_swagger_full.json +51112 -0
- esi/tests/test_tasks.py +116 -0
- esi/tests/test_templatetags.py +22 -0
- esi/tests/test_views.py +331 -0
- esi/tests/threading_pilot.py +69 -0
- esi/urls.py +9 -0
- 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)
|