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
esi/admin.py ADDED
@@ -0,0 +1,42 @@
1
+ from django.contrib import admin
2
+ from django.contrib.auth import get_user_model
3
+ from django.db.models import QuerySet
4
+
5
+ from .models import CallbackRedirect, Scope, Token
6
+
7
+ admin.site.register(CallbackRedirect)
8
+
9
+
10
+ @admin.register(Scope)
11
+ class ScopeAdmin(admin.ModelAdmin):
12
+ list_display = ('name', 'help_text')
13
+
14
+
15
+ @admin.register(Token)
16
+ class TokenAdmin(admin.ModelAdmin):
17
+ def get_queryset(self, request) -> QuerySet["Token"]:
18
+ qs = super().get_queryset(request)
19
+ return qs.select_related('user').prefetch_related('scopes')
20
+
21
+ @admin.display(
22
+ description='Scopes'
23
+ )
24
+ def get_scopes(self, obj) -> str:
25
+ return ", ".join([x.name for x in obj.scopes.all()])
26
+
27
+ User = get_user_model()
28
+ list_display = ('user', 'character_name', 'get_scopes')
29
+ search_fields = ['user__%s' % User.USERNAME_FIELD, 'character_name', 'scopes__name']
30
+ list_filter = (
31
+ ('user', admin.RelatedOnlyFieldListFilter),
32
+ 'character_name'
33
+ )
34
+ ordering = ('user',)
35
+ readonly_fields = (
36
+ 'character_id',
37
+ 'character_name',
38
+ 'token_type',
39
+ 'character_owner_hash',
40
+ 'scopes',
41
+ 'sso_version'
42
+ )
@@ -0,0 +1,79 @@
1
+ import datetime as dt
2
+ from hashlib import md5
3
+
4
+ from httpx import URL, Client, Response
5
+ from httpx._client import USE_CLIENT_DEFAULT, UseClientDefault
6
+ from httpx._types import (
7
+ AuthTypes, CookieTypes, HeaderTypes, QueryParamTypes, RequestExtensions,
8
+ TimeoutTypes,
9
+ )
10
+
11
+ from django.core.cache import cache
12
+ from django.utils import timezone
13
+
14
+
15
+ class SpecCachingClient(Client):
16
+
17
+ def _is_spec(self, url: URL | str) -> bool:
18
+ return "https://esi.evetech.net/meta/openapi" in str(url)
19
+
20
+ def _get_seconds_to_dt(self) -> int:
21
+ expire_time = timezone.now()
22
+ if expire_time.hour > 11:
23
+ expire_time += dt.timedelta(hours=24)
24
+
25
+ expire_time = expire_time.replace(hour=11, minute=30, second=0)
26
+
27
+ return int((expire_time - timezone.now()).total_seconds())
28
+
29
+ def _get_spec_cache(self, url, ):
30
+ return cache.get(self._get_api_cache_key(url), False)
31
+
32
+ def _set_spec_cache(self, url, body):
33
+ ttl = self._get_seconds_to_dt()
34
+ cache.set(self._get_api_cache_key(url), body, ttl)
35
+
36
+ def _get_api_cache_key(self, url: str) -> str:
37
+ """
38
+ provide a unique token for the spec url with compat date.
39
+ """
40
+ compat_date = self.headers.get("X-Compatibility-Date", None)
41
+ return f"ESI_API_CACHE_{md5(f'{url}-{compat_date}'.encode()).hexdigest()}"
42
+
43
+ def get(
44
+ self,
45
+ url: URL | str,
46
+ *,
47
+ params: QueryParamTypes | None = None,
48
+ headers: HeaderTypes | None = None,
49
+ cookies: CookieTypes | None = None,
50
+ auth: AuthTypes | UseClientDefault | None = USE_CLIENT_DEFAULT,
51
+ follow_redirects: bool | UseClientDefault = USE_CLIENT_DEFAULT,
52
+ timeout: TimeoutTypes | UseClientDefault = USE_CLIENT_DEFAULT,
53
+ extensions: RequestExtensions | None = None,
54
+ ) -> Response:
55
+ if self._is_spec(url):
56
+ _spec = self._get_spec_cache(url)
57
+ if not _spec:
58
+ _spec = super().get(
59
+ url,
60
+ params=params,
61
+ headers=headers,
62
+ cookies=cookies,
63
+ auth=auth,
64
+ follow_redirects=follow_redirects,
65
+ timeout=timeout,
66
+ extensions=extensions
67
+ )
68
+ self._set_spec_cache(url, _spec)
69
+ return _spec
70
+ return super().get(
71
+ url,
72
+ params=params,
73
+ headers=headers,
74
+ cookies=cookies,
75
+ auth=auth,
76
+ follow_redirects=follow_redirects,
77
+ timeout=timeout,
78
+ extensions=extensions
79
+ )
@@ -0,0 +1,224 @@
1
+ import logging
2
+ from typing import Any
3
+
4
+ from collections.abc import Generator
5
+ from django.conf import settings
6
+
7
+ from aiopenapi3.plugin import Document, Init
8
+
9
+ logger = logging.getLogger(__name__)
10
+
11
+
12
+ class Trim204ContentType(Document):
13
+ """
14
+ Removes and content-type from responses on a 204 reponses
15
+ A 204 never has content...
16
+ """
17
+
18
+ def parsed(self, ctx: Document.Context) -> Document.Context:
19
+ spec = ctx.document
20
+ # Patch all paths
21
+ for path_item in spec.get("paths", {}).values():
22
+ for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
23
+ method = path_item.get(method_name)
24
+ if not method:
25
+ continue
26
+ if "204" in method['responses']:
27
+ method['responses']["204"].pop("content", [])
28
+ return ctx
29
+
30
+
31
+ def find_refs_recursively(data: Any, parent: Any = None) -> Generator[Any, None, None]:
32
+ """
33
+ Recursively searches for all instances of "#ref" in a dict+children and returns schemas.
34
+ """
35
+ if isinstance(data, dict):
36
+ for key, value in data.items():
37
+ if key == "$ref":
38
+ if "#/components/schemas/" in value:
39
+ next = value.split("/")[-1]
40
+ yield next
41
+ if parent:
42
+ yield from find_refs_recursively(parent[next], parent)
43
+ yield from find_refs_recursively(value, parent)
44
+ elif isinstance(data, list):
45
+ for item in data:
46
+ yield from find_refs_recursively(item, parent)
47
+
48
+
49
+ class MinifySpec(Document):
50
+ """
51
+ Removes operations and schemas from spec to limit memory spam
52
+ """
53
+
54
+ def __init__(self, tags: list[str], operations: list[str]):
55
+ super().__init__()
56
+ self.keep_tags = set(tags)
57
+ self.keep_ops = operations
58
+
59
+ def parsed(self, ctx: Document.Context) -> Document.Context:
60
+
61
+ if len(self.keep_tags) == 0 and self.keep_ops == []:
62
+ logger.error("No tag/path filtering supplied to ESI Client. Using all tags. This throw an error with `DEBUG=False`!", stack_info=True)
63
+ if not getattr(settings, "DEBUG", False):
64
+ # we are in production mode throw error to protect RAM.
65
+ raise AttributeError("No tag/path filtering supplied to ESI Client.")
66
+ # We are in debug mode allow an unfiltered client.
67
+ return ctx
68
+
69
+ # filter the client.
70
+ spec = ctx.document
71
+
72
+ remove_paths = set()
73
+ keep_schema = set()
74
+ logger.debug("Filtering Paths/Tags: ")
75
+ for name, path_item in spec.get("paths", {}).items():
76
+ keep = False
77
+ for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
78
+ method = path_item.get(method_name)
79
+ if not method:
80
+ continue
81
+ if len(self.keep_tags.intersection(method['tags'])) or method["operationId"] in self.keep_ops:
82
+ keep = True
83
+ schemas = find_refs_recursively(
84
+ path_item,
85
+ spec["components"]["schemas"] # find all sub schema's
86
+ )
87
+ for s in schemas:
88
+ keep_schema.add(s)
89
+
90
+ if not keep:
91
+ remove_paths.add(name)
92
+ else:
93
+ logger.debug(f" - {name}")
94
+
95
+ # remove the paths we don't care for
96
+ for name in remove_paths:
97
+ spec["paths"].pop(name)
98
+
99
+ # build new schema from what we need
100
+ logger.debug("Rebuilding Schema: ")
101
+ new_schema = {}
102
+ for name, data in spec["components"]["schemas"].items():
103
+ if name in keep_schema:
104
+ logger.debug(f" - {name}")
105
+ new_schema[name] = data
106
+
107
+ # replace schemas with the new schema
108
+ ctx.document["components"]["schemas"] = new_schema
109
+
110
+ return ctx
111
+
112
+
113
+ class Add304ContentType(Document):
114
+ """
115
+ Adds 304 content-type to responses
116
+ A 304 never has content. ESI defualt has application/json
117
+ This is a hack for now
118
+ """
119
+
120
+ def parsed(self, ctx: Document.Context) -> Document.Context:
121
+ spec = ctx.document
122
+ # Patch all paths
123
+ for path_item in spec.get("paths", {}).values():
124
+ for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
125
+ method = path_item.get(method_name)
126
+ if not method:
127
+ continue
128
+ if "304" not in method['responses']:
129
+ method['responses']["304"] = {
130
+ "description": "Not Modified"
131
+ }
132
+ return ctx
133
+
134
+
135
+ class RemoveSecurityParameter(Document):
136
+ """
137
+ Removes the whole OAuth2 securityScheme
138
+ """
139
+
140
+ def parsed(self, ctx: Document.Context) -> Document.Context:
141
+ print("RemoveSecurityParameterPlugin: Removing OAuth2 securityScheme")
142
+ spec = ctx.document
143
+ oauth2 = spec.get("components", {}).get("securitySchemes", {}).pop("OAuth2", None)
144
+ # Patch all paths
145
+ for path_item in spec.get("paths", {}).values():
146
+ for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
147
+ method = path_item.get(method_name)
148
+ if not method:
149
+ continue
150
+ method.pop("security", None)
151
+
152
+ return ctx
153
+
154
+
155
+ class TrimSecurityParameter(Document):
156
+ """TrimSecurityParameter
157
+ Trims out of Spec OAuth2 attributes. CCP have fixed this.
158
+ Leaving in place in case we need a quick reference again.
159
+ """
160
+
161
+ def parsed(self, ctx: Document.Context) -> Document.Context:
162
+ print("TrimSecurityParameter: Trimming out of spec attributes")
163
+ spec = ctx.document
164
+ oauth2 = spec.get("components", {}).get("securitySchemes", {}).get("OAuth2")
165
+ if oauth2 and oauth2.get("type") == "oauth2":
166
+ oauth2.pop("in", None)
167
+ oauth2.pop("name", None)
168
+ return ctx
169
+
170
+
171
+ class PatchCompatibilityDatePlugin(Document):
172
+ """
173
+ Makes the X-Compatibility-Date header optional
174
+
175
+ This is because WE specifically add it in the library to the HTTP requests,
176
+ but without this, it will be a required parameter during request generation before it hits the HTTP library.
177
+ """
178
+
179
+ def parsed(self, ctx: Document.Context) -> Document.Context:
180
+ logger.debug("PatchCompatibilityDatePlugin: making compatibility date optional")
181
+ spec = ctx.document
182
+
183
+ def patch_param(param):
184
+ # Follow $ref if present
185
+ if "$ref" in param:
186
+ ref = param["$ref"]
187
+ parts = ref.split("/")
188
+ if parts[1] == "components" and parts[2] == "parameters":
189
+ param_name = parts[-1]
190
+ comp_param = spec.get("components", {}).get("parameters", {}).get(param_name)
191
+ if comp_param and comp_param.get("name") == "X-Compatibility-Date":
192
+ comp_param["required"] = False
193
+ else:
194
+ # Inline parameter
195
+ if (param.get("name") == "X-Compatibility-Date" and param.get("in") == "header"):
196
+ param["required"] = False
197
+
198
+ # Patch all paths
199
+ for path_item in spec.get("paths", {}).values():
200
+ for method_name in ("get", "post", "put", "delete", "patch", "options", "head"):
201
+ method = path_item.get(method_name)
202
+ if not method:
203
+ continue
204
+ for param in method.get("parameters", []):
205
+ patch_param(param)
206
+
207
+ # Patch global parameters in components
208
+ for param_name, param in spec.get("components", {}).get("parameters", {}).items():
209
+ if param.get("name") == "X-Compatibility-Date" and param.get("in") == "header":
210
+ param["required"] = False
211
+
212
+ return ctx
213
+
214
+
215
+ class DjangoESIInit(Init):
216
+
217
+ def __init__(self, ua_appname):
218
+ super().__init__()
219
+ self.app_name = ua_appname
220
+
221
+ def initialized(self, ctx: Init.Context) -> Init.Context:
222
+ # Force the app_name into the api client class for etags
223
+ self.api.app_name = self.app_name
224
+ return ctx # noqa
esi/app_settings.py ADDED
@@ -0,0 +1,112 @@
1
+ from django.conf import settings
2
+
3
+ ESI_SSO_CLIENT_ID = getattr(settings, 'ESI_SSO_CLIENT_ID', None)
4
+ """Client ID of your Eve Online SSO app. REQUIRED."""
5
+
6
+ ESI_SSO_CLIENT_SECRET = getattr(settings, 'ESI_SSO_CLIENT_SECRET', None)
7
+ """Client Secret of your Eve Online SSO app. REQUIRED."""
8
+
9
+ ESI_SSO_CALLBACK_URL = getattr(settings, 'ESI_SSO_CALLBACK_URL', None)
10
+ """Callback for your Eve Online SSO app. REQUIRED."""
11
+
12
+ # ESI_SSO_CLIENT_ID, ESI_SSO_CLIENT_SECRET ESI_SSO_CALLBACK_URL
13
+ # are required for SSO to function.
14
+ # Can be left blank if settings.DEBUG is set to True
15
+
16
+ ESI_API_DATASOURCE = getattr(settings, 'ESI_API_DATASOURCE', 'tranquility')
17
+ """Change these to switch to Singularity."""
18
+
19
+ ESI_API_VERSION = getattr(settings, 'ESI_API_VERSION', 'latest')
20
+ """Change this to access different revisions of the ESI API by default"""
21
+
22
+ ESI_ALWAYS_CREATE_TOKEN = getattr(settings, 'ESI_ALWAYS_CREATE_TOKEN', False)
23
+ """Enable to force new token creation every callback."""
24
+
25
+ ESI_CACHE_RESPONSE = getattr(settings, 'ESI_CACHE_RESPONSE', True)
26
+ """Disable to stop caching endpoint responses."""
27
+
28
+ ESI_INFO_LOGGING_ENABLED = getattr(settings, 'ESI_INFO_LOGGING_ENABLED', False)
29
+ """Enable/disable verbose info logging."""
30
+
31
+ ESI_LOG_LEVEL_LIBRARIES = str(getattr(settings, 'ESI_LOG_LEVEL_LIBRARIES', 'INFO'))
32
+ """Set log level for libraries like bravado and urlib3."""
33
+
34
+ ESI_DEBUG_RESPONSE_CONTENT_LOGGING = getattr(settings, 'ESI_DEBUG_RESPONSE_CONTENT_LOGGING', True)
35
+ """Enable/Disable logging of ESI response contents."""
36
+
37
+ ESI_USER_CONTACT_EMAIL = getattr(settings, 'ESI_USER_CONTACT_EMAIL', None)
38
+ """Contact email address of server owner.
39
+
40
+ This will be included in the User-Agent header of every request.
41
+ """
42
+
43
+ ESI_CONNECTION_POOL_MAXSIZE = getattr(settings, 'ESI_CONNECTION_POOL_MAXSIZE', 10)
44
+ """Max size of the connection pool.
45
+
46
+ Increase this setting if you hav more parallel
47
+ threads connected to ESI at the same time.
48
+ """
49
+
50
+ ESI_CONNECTION_ERROR_MAX_RETRIES = getattr(
51
+ settings, 'ESI_CONNECTION_ERROR_MAX_RETRIES', 3
52
+ )
53
+ """Max retries on failed connections."""
54
+
55
+ ESI_SERVER_ERROR_MAX_RETRIES = getattr(settings, 'ESI_SERVER_ERROR_MAX_RETRIES', 3)
56
+ """Max retries on server errors."""
57
+
58
+ ESI_SERVER_ERROR_BACKOFF_FACTOR = getattr(
59
+ settings, 'ESI_SERVER_ERROR_BACKOFF_FACTOR', 0.2
60
+ )
61
+ """Backoff factor for retries on server error."""
62
+
63
+ ESI_REQUESTS_CONNECT_TIMEOUT = getattr(settings, 'ESI_REQUESTS_CONNECT_TIMEOUT', 5)
64
+ """Default connection timeouts for all requests to ESI.
65
+
66
+ Can temporarily overwritten with by passing ``timeout`` with ``result()``
67
+ """
68
+
69
+ ESI_REQUESTS_READ_TIMEOUT = getattr(settings, 'ESI_REQUESTS_READ_TIMEOUT', 10)
70
+ """Default read timeouts for all requests to ESI.
71
+ This should be a maximum of 10s as ESI cuts all requests to the monolith off @ 10s.
72
+
73
+ Can temporarily overwritten with by passing ``timeout`` with ``result()``
74
+ """
75
+
76
+ ESI_REQUESTS_WRITE_TIMEOUT = getattr(settings, 'ESI_REQUESTS_WRITE_TIMEOUT', 5)
77
+ """Default write timeouts for all requests to ESI.
78
+
79
+ Can temporarily overwritten with by passing ``timeout`` with ``result()``
80
+ """
81
+
82
+ ESI_REQUESTS_POOL_TIMEOUT = getattr(settings, 'ESI_REQUESTS_POOL_TIMEOUT', 5)
83
+ """Default pool timeouts for all requests to ESI.
84
+
85
+ Can temporarily overwritten with by passing ``timeout`` with ``result()``
86
+ """
87
+
88
+ # These probably won't ever change. Override if needed.
89
+ ESI_API_URL = getattr(settings, 'ESI_API_URL', 'https://esi.evetech.net/')
90
+ ESI_OAUTH_URL = getattr(
91
+ settings, 'ESI_SSO_BASE_URL', 'https://login.eveonline.com/v2/oauth'
92
+ )
93
+ ESI_OAUTH_LOGIN_URL = getattr(
94
+ settings, 'ESI_SSO_LOGIN_URL', ESI_OAUTH_URL + "/authorize/"
95
+ )
96
+ ESI_TOKEN_URL = getattr(settings, 'ESI_CODE_EXCHANGE_URL', ESI_OAUTH_URL + "/token")
97
+
98
+ ESI_TOKEN_VERIFY_URL = getattr(
99
+ settings, 'ESI_TOKEN_EXCHANGE_URL', ESI_OAUTH_URL + "/verify"
100
+ ) # deprecated
101
+ ESI_TOKEN_JWK_SET_URL = "https://login.eveonline.com/oauth/jwks"
102
+
103
+ ESI_TOKEN_VALID_DURATION = int(getattr(settings, 'ESI_TOKEN_VALID_DURATION', 1170))
104
+ ESI_SPEC_CACHE_DURATION = int(getattr(settings, 'ESI_SPEC_CACHE_DURATION', 3600))
105
+
106
+ # Audience claim for JWTs
107
+ ESI_TOKEN_JWT_AUDIENCE = str(getattr(settings, "ESI_TOKEN_JWT_AUDIENCE", "EVE Online"))
108
+
109
+ # list of all official language codes supported by ESI
110
+ ESI_LANGUAGES = getattr(settings, 'ESI_LANGUAGES', [
111
+ 'en', 'de', 'fr', 'ja', 'ru', 'zh', 'ko', 'es'
112
+ ])
esi/apps.py ADDED
@@ -0,0 +1,11 @@
1
+ from django.apps import AppConfig
2
+
3
+
4
+ class EsiConfig(AppConfig):
5
+ default_auto_field = 'django.db.models.AutoField'
6
+ name = 'esi'
7
+ verbose_name = 'EVE Swagger Interface (SSO v2)'
8
+
9
+ def ready(self) -> None:
10
+ super().ready()
11
+ from esi import checks # noqa
esi/checks.py ADDED
@@ -0,0 +1,56 @@
1
+ from django.conf import settings
2
+ from django.core.checks import Error, Tags, Warning, register
3
+
4
+
5
+ @register(Tags.security)
6
+ def check_sso_application_settings(*args, **kwargs):
7
+
8
+ errors = []
9
+
10
+ if (
11
+ not hasattr(settings, "ESI_SSO_CLIENT_ID")
12
+ or not hasattr(settings, "ESI_SSO_CLIENT_SECRET")
13
+ or not hasattr(settings, "ESI_SSO_CALLBACK_URL")
14
+ ):
15
+ if settings.DEBUG:
16
+ errors.append(
17
+ Warning(
18
+ 'ESI SSO application settings are not configured.',
19
+ hint='SSO features will not work.',
20
+ id='esi.W001'
21
+ )
22
+ )
23
+ else:
24
+ errors.append(
25
+ Error(
26
+ 'ESI SSO application settings cannot be blank.',
27
+ hint=(
28
+ 'Register an application at https://developers.eveonline.com '
29
+ 'and add ESI_SSO_CLIENT_ID, ESI_SSO_CLIENT_SECRET, and '
30
+ 'ESI_SSO_CALLBACK_URL to your project settings.'
31
+ ),
32
+ id='esi.E001'
33
+ )
34
+ )
35
+
36
+ # Check for ESI_USER_CONTACT_EMAIL
37
+ if hasattr(settings, "ESI_USER_CONTACT_EMAIL"):
38
+ # Check if ESI_USER_CONTACT_EMAIL is empty
39
+ if settings.ESI_USER_CONTACT_EMAIL == "":
40
+ errors.append(
41
+ Error(
42
+ msg="'ESI_USER_CONTACT_EMAIL' is empty. A valid email is required as maintainer contact for CCP.",
43
+ hint="",
44
+ id="esi.E002",
45
+ )
46
+ )
47
+ # ESI_USER_CONTACT_EMAIL not found
48
+ else:
49
+ errors.append(
50
+ Error(
51
+ msg="No 'ESI_USER_CONTACT_EMAIL' found is settings. A valid email is required as maintainer contact for CCP.",
52
+ hint="",
53
+ id="esi.E003",
54
+ )
55
+ )
56
+ return errors