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
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
|
+
)
|
esi/aiopenapi3/client.py
ADDED
|
@@ -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
|