django-esi 6.0.0__tar.gz → 7.0.0b1__tar.gz
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.
Potentially problematic release.
This version of django-esi might be problematic. Click here for more details.
- {django_esi-6.0.0 → django_esi-7.0.0b1}/PKG-INFO +21 -19
- {django_esi-6.0.0 → django_esi-7.0.0b1}/README.md +13 -13
- django_esi-7.0.0b1/esi/__init__.py +5 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/admin.py +0 -1
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/apps.py +1 -1
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/checks.py +23 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/clients.py +62 -32
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/decorators.py +2 -1
- django_esi-7.0.0b1/esi/migrations/0013_squashed_0012_fix_token_type_choices.py +57 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/models.py +8 -7
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tasks.py +29 -2
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/templates/esi/select_token.html +9 -9
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/factories.py +1 -1
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_clients.py +122 -98
- django_esi-7.0.0b1/esi/tests/test_tasks.py +116 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/pyproject.toml +10 -5
- django_esi-6.0.0/esi/__init__.py +0 -5
- django_esi-6.0.0/esi/tests/test_tasks.py +0 -61
- {django_esi-6.0.0 → django_esi-7.0.0b1}/LICENSE +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/app_settings.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/errors.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/de/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/de/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/en/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/en/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/es/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/es/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/fr_FR/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/fr_FR/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/it_IT/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/it_IT/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ja/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ja/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ko_KR/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ko_KR/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ru/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/ru/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/zh_Hans/LC_MESSAGES/django.mo +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/locale/zh_Hans/LC_MESSAGES/django.po +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/management/commands/__init__.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/management/commands/migrate_to_ssov2.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/managers.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0001_initial.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0002_scopes_20161208.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0003_hide_tokens_from_admin_site.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0004_remove_unique_access_token.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0005_remove_token_length_limit.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0006_remove_url_length_limit.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0007_fix_mysql_8_migration.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0008_nullable_refresh_token.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0009_set_old_tokens_to_sso_v1.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0010_set_new_tokens_to_sso_v2.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0011_add_token_indices.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/0012_fix_token_type_choices.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/migrations/__init__.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Large_Black.png +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Large_White.png +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Small_Black.png +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/static/esi/img/EVE_SSO_Login_Buttons_Small_White.png +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/templatetags/__init__.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/templatetags/scope_tags.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/__init__.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/client_authed_pilot.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/client_public_pilot.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/factories_2.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/jwt_factory.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_checks.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_decorators.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_management_command.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_managers.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_models.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_swagger.json +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_swagger_full.json +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_templatetags.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/test_views.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/tests/threading_pilot.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/urls.py +0 -0
- {django_esi-6.0.0 → django_esi-7.0.0b1}/esi/views.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
Metadata-Version: 2.
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
2
|
Name: django-esi
|
|
3
|
-
Version:
|
|
3
|
+
Version: 7.0.0b1
|
|
4
4
|
Summary: Django app for accessing the EVE Swagger Interface (ESI).
|
|
5
5
|
Author-email: Alliance Auth <adarnof@gmail.com>
|
|
6
6
|
Requires-Python: >=3.8
|
|
@@ -8,8 +8,8 @@ Description-Content-Type: text/markdown
|
|
|
8
8
|
Classifier: Environment :: Web Environment
|
|
9
9
|
Classifier: Framework :: Django
|
|
10
10
|
Classifier: Framework :: Django :: 4.2
|
|
11
|
-
Classifier: Framework :: Django :: 5.0
|
|
12
11
|
Classifier: Framework :: Django :: 5.1
|
|
12
|
+
Classifier: Framework :: Django :: 5.2
|
|
13
13
|
Classifier: Intended Audience :: Developers
|
|
14
14
|
Classifier: License :: OSI Approved :: GNU General Public License v3 (GPLv3)
|
|
15
15
|
Classifier: Operating System :: OS Independent
|
|
@@ -20,12 +20,14 @@ Classifier: Programming Language :: Python :: 3.9
|
|
|
20
20
|
Classifier: Programming Language :: Python :: 3.10
|
|
21
21
|
Classifier: Programming Language :: Python :: 3.11
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.12
|
|
23
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
23
24
|
Classifier: Topic :: Internet :: WWW/HTTP
|
|
24
25
|
Classifier: Topic :: Internet :: WWW/HTTP :: Dynamic Content
|
|
25
|
-
|
|
26
|
+
License-File: LICENSE
|
|
27
|
+
Requires-Dist: bravado>=10.6,<12
|
|
26
28
|
Requires-Dist: brotli
|
|
27
29
|
Requires-Dist: celery>=4.0.2
|
|
28
|
-
Requires-Dist: django>=4.2,<
|
|
30
|
+
Requires-Dist: django>=4.2,<6
|
|
29
31
|
Requires-Dist: jsonschema<4
|
|
30
32
|
Requires-Dist: python-jose>=3.3
|
|
31
33
|
Requires-Dist: requests>=2.26
|
|
@@ -34,7 +36,7 @@ Requires-Dist: tqdm>=4.62.3
|
|
|
34
36
|
Requires-Dist: myst-parser ; extra == "docs"
|
|
35
37
|
Requires-Dist: sphinx ; extra == "docs"
|
|
36
38
|
Requires-Dist: sphinx-copybutton ; extra == "docs"
|
|
37
|
-
Requires-Dist: sphinx-rtd-theme
|
|
39
|
+
Requires-Dist: sphinx-rtd-theme>=3,<4 ; extra == "docs"
|
|
38
40
|
Requires-Dist: sphinx-tabs ; extra == "docs"
|
|
39
41
|
Requires-Dist: sphinxcontrib-django ; extra == "docs"
|
|
40
42
|
Requires-Dist: coverage ; extra == "test"
|
|
@@ -47,27 +49,27 @@ Project-URL: Tracker, https://gitlab.com/allianceauth/django-esi/-/issues
|
|
|
47
49
|
Provides-Extra: docs
|
|
48
50
|
Provides-Extra: test
|
|
49
51
|
|
|
50
|
-
#
|
|
52
|
+
# Django-ESI
|
|
51
53
|
|
|
52
54
|
Django app for easy access to the EVE Swagger Interface (ESI)
|
|
53
55
|
|
|
54
|
-
[](https://pypi.org/project/django-esi/)
|
|
57
|
+
[](https://pypi.org/project/django-esi/)
|
|
58
|
+
[](https://pypi.org/project/django-esi/)
|
|
59
|
+
[](https://pypi.org/project/django-esi/)
|
|
60
|
+
[](https://gitlab.com/allianceauth/django-esi/pipelines)
|
|
61
|
+
[](https://gitlab.com/allianceauth/django-esi/pipelines)
|
|
60
62
|
[](https://django-esi.readthedocs.io/en/latest/?badge=latest)
|
|
61
63
|
[](https://github.com/pre-commit/pre-commit)
|
|
62
64
|
[](https://discord.gg/fjnHAmk)
|
|
63
65
|
|
|
64
66
|
## Overview
|
|
65
67
|
|
|
66
|
-
Django-
|
|
68
|
+
Django-ESI is a Django app that provides an interface for easy access to the EVE Swagger Interface (ESI), the official API for the game [EVE Online](https://www.eveonline.com/).
|
|
67
69
|
|
|
68
|
-
It is
|
|
70
|
+
It is built upon [Bravado](https://github.com/Yelp/bravado) - a python client library for Swagger 2.0 services.
|
|
69
71
|
|
|
70
|
-
Django-
|
|
72
|
+
Django-ESI adds the following main functionalities to a Django site:
|
|
71
73
|
|
|
72
74
|
- Dynamically generated client for interacting with public and private ESI endpoints
|
|
73
75
|
- Support for adding EVE SSO to authenticate characters and retrieve tokens
|
|
@@ -75,8 +77,8 @@ Django-esi adds the following main functionalities to a Django site:
|
|
|
75
77
|
|
|
76
78
|
## Python Support
|
|
77
79
|
|
|
78
|
-
Django-
|
|
79
|
-
<https://docs.djangoproject.com/en/5.
|
|
80
|
+
Django-ESI follows the Django Python support schedule, The supported version of Python will differ based on the version of Django used.
|
|
81
|
+
<https://docs.djangoproject.com/en/5.2/faq/install/#what-python-version-can-i-use-with-django>
|
|
80
82
|
|
|
81
83
|
## History of this app
|
|
82
84
|
|
|
@@ -84,5 +86,5 @@ This app is a fork from [adarnauth-esi](https://gitlab.com/Adarnof/adarnauth-esi
|
|
|
84
86
|
|
|
85
87
|
## Documentation
|
|
86
88
|
|
|
87
|
-
For all details on how to install and use
|
|
89
|
+
For all details on how to install and use Django-ESI please see the [Documentation](https://django-esi.readthedocs.io/en/latest/).
|
|
88
90
|
|
|
@@ -1,24 +1,24 @@
|
|
|
1
|
-
#
|
|
1
|
+
# Django-ESI
|
|
2
2
|
|
|
3
3
|
Django app for easy access to the EVE Swagger Interface (ESI)
|
|
4
4
|
|
|
5
|
-
[](https://pypi.org/project/django-esi/)
|
|
6
|
+
[](https://pypi.org/project/django-esi/)
|
|
7
|
+
[](https://pypi.org/project/django-esi/)
|
|
8
|
+
[](https://pypi.org/project/django-esi/)
|
|
9
|
+
[](https://gitlab.com/allianceauth/django-esi/pipelines)
|
|
10
|
+
[](https://gitlab.com/allianceauth/django-esi/pipelines)
|
|
11
11
|
[](https://django-esi.readthedocs.io/en/latest/?badge=latest)
|
|
12
12
|
[](https://github.com/pre-commit/pre-commit)
|
|
13
13
|
[](https://discord.gg/fjnHAmk)
|
|
14
14
|
|
|
15
15
|
## Overview
|
|
16
16
|
|
|
17
|
-
Django-
|
|
17
|
+
Django-ESI is a Django app that provides an interface for easy access to the EVE Swagger Interface (ESI), the official API for the game [EVE Online](https://www.eveonline.com/).
|
|
18
18
|
|
|
19
|
-
It is
|
|
19
|
+
It is built upon [Bravado](https://github.com/Yelp/bravado) - a python client library for Swagger 2.0 services.
|
|
20
20
|
|
|
21
|
-
Django-
|
|
21
|
+
Django-ESI adds the following main functionalities to a Django site:
|
|
22
22
|
|
|
23
23
|
- Dynamically generated client for interacting with public and private ESI endpoints
|
|
24
24
|
- Support for adding EVE SSO to authenticate characters and retrieve tokens
|
|
@@ -26,8 +26,8 @@ Django-esi adds the following main functionalities to a Django site:
|
|
|
26
26
|
|
|
27
27
|
## Python Support
|
|
28
28
|
|
|
29
|
-
Django-
|
|
30
|
-
<https://docs.djangoproject.com/en/5.
|
|
29
|
+
Django-ESI follows the Django Python support schedule, The supported version of Python will differ based on the version of Django used.
|
|
30
|
+
<https://docs.djangoproject.com/en/5.2/faq/install/#what-python-version-can-i-use-with-django>
|
|
31
31
|
|
|
32
32
|
## History of this app
|
|
33
33
|
|
|
@@ -35,4 +35,4 @@ This app is a fork from [adarnauth-esi](https://gitlab.com/Adarnof/adarnauth-esi
|
|
|
35
35
|
|
|
36
36
|
## Documentation
|
|
37
37
|
|
|
38
|
-
For all details on how to install and use
|
|
38
|
+
For all details on how to install and use Django-ESI please see the [Documentation](https://django-esi.readthedocs.io/en/latest/).
|
|
@@ -23,7 +23,6 @@ class TokenAdmin(admin.ModelAdmin):
|
|
|
23
23
|
def get_scopes(self, obj):
|
|
24
24
|
return ", ".join([x.name for x in obj.scopes.all()])
|
|
25
25
|
|
|
26
|
-
|
|
27
26
|
User = get_user_model()
|
|
28
27
|
list_display = ('user', 'character_name', 'get_scopes')
|
|
29
28
|
search_fields = ['user__%s' % User.USERNAME_FIELD, 'character_name', 'scopes__name']
|
|
@@ -4,7 +4,9 @@ from django.conf import settings
|
|
|
4
4
|
|
|
5
5
|
@register(Tags.security)
|
|
6
6
|
def check_sso_application_settings(*args, **kwargs):
|
|
7
|
+
|
|
7
8
|
errors = []
|
|
9
|
+
|
|
8
10
|
if (
|
|
9
11
|
not hasattr(settings, "ESI_SSO_CLIENT_ID")
|
|
10
12
|
or not hasattr(settings, "ESI_SSO_CLIENT_SECRET")
|
|
@@ -30,4 +32,25 @@ def check_sso_application_settings(*args, **kwargs):
|
|
|
30
32
|
id='esi.E001'
|
|
31
33
|
)
|
|
32
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
|
+
)
|
|
33
56
|
return errors
|
|
@@ -5,6 +5,7 @@ import logging
|
|
|
5
5
|
from time import sleep
|
|
6
6
|
from urllib import parse as urlparse
|
|
7
7
|
from typing import Any, Union, Tuple
|
|
8
|
+
import warnings
|
|
8
9
|
|
|
9
10
|
from bravado.client import SwaggerClient
|
|
10
11
|
from bravado import requests_client
|
|
@@ -20,7 +21,7 @@ from requests.adapters import HTTPAdapter
|
|
|
20
21
|
from django.core.cache import cache
|
|
21
22
|
|
|
22
23
|
from .errors import TokenExpiredError
|
|
23
|
-
from . import app_settings, __version__, __title__
|
|
24
|
+
from . import app_settings, __version__, __title__, __url__
|
|
24
25
|
|
|
25
26
|
|
|
26
27
|
logger = logging.getLogger(__name__)
|
|
@@ -438,13 +439,9 @@ def read_spec(path, http_client=None):
|
|
|
438
439
|
|
|
439
440
|
|
|
440
441
|
def esi_client_factory(
|
|
441
|
-
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
version: str = None,
|
|
445
|
-
app_info_text: str = None,
|
|
446
|
-
**kwargs
|
|
447
|
-
) -> SwaggerClient:
|
|
442
|
+
token=None, datasource: str = None, spec_file: str = None, version: str = None,
|
|
443
|
+
app_info_text: str = None, # Deprecate in favour of the following variables
|
|
444
|
+
ua_appname: str = None, ua_version: str = None, ua_url: str = None, **kwargs) -> SwaggerClient:
|
|
448
445
|
"""Generate a new ESI client.
|
|
449
446
|
|
|
450
447
|
Args:
|
|
@@ -452,10 +449,9 @@ def esi_client_factory(
|
|
|
452
449
|
datasource: Name of the ESI datasource to access.
|
|
453
450
|
spec_file: Absolute path to a swagger spec file to load.
|
|
454
451
|
version: Base ESI API version. Accepted values are 'legacy', 'latest',
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
Note that spaces are used as delimiter.
|
|
452
|
+
ua_appname: Name of the App for generating a User-Agent,
|
|
453
|
+
ua_version: Version of the App for generating a User-Agent,
|
|
454
|
+
ua_url: (optional) URL To the Source Code or Documentation for generating a User-Agent,
|
|
459
455
|
kwargs: Explicit resource versions to build, in the form Character='v4'. \
|
|
460
456
|
Same values accepted as version.
|
|
461
457
|
|
|
@@ -466,15 +462,40 @@ def esi_client_factory(
|
|
|
466
462
|
Returns:
|
|
467
463
|
New ESI client
|
|
468
464
|
"""
|
|
465
|
+
|
|
466
|
+
if app_info_text is not None:
|
|
467
|
+
warnings.warn(
|
|
468
|
+
"The 'app_info_text' parameter is deprecated and will be removed in a future release. "
|
|
469
|
+
"Use 'ua_appname', 'ua_version', and `ua_url` to dynamically build a User-Agent instead",
|
|
470
|
+
DeprecationWarning,
|
|
471
|
+
stacklevel=2
|
|
472
|
+
)
|
|
473
|
+
|
|
474
|
+
if ua_appname is None or ua_version is None:
|
|
475
|
+
warnings.warn(
|
|
476
|
+
"Applications must define their own 'ua_appname' and 'ua_version' to generate a User-Agent",
|
|
477
|
+
DeprecationWarning,
|
|
478
|
+
stacklevel=2
|
|
479
|
+
)
|
|
480
|
+
|
|
469
481
|
if app_settings.ESI_INFO_LOGGING_ENABLED:
|
|
470
482
|
logger.info('Generating an ESI client...')
|
|
471
483
|
|
|
472
484
|
client = RequestsClientPlus()
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
476
|
-
|
|
477
|
-
user_agent
|
|
485
|
+
|
|
486
|
+
if app_info_text:
|
|
487
|
+
# app_info_text (email@example) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
|
|
488
|
+
# Deprecated
|
|
489
|
+
user_agent = f"{app_info_text} ({app_settings.ESI_USER_CONTACT_EMAIL}) {__title__}/{__version__} (+{__url__})"
|
|
490
|
+
elif ua_appname is None or ua_version is None:
|
|
491
|
+
# Django-ESI/1.2.3 () (email@example; +https://gitlab.com/allianceauth/django-esi)
|
|
492
|
+
# Deprecated
|
|
493
|
+
user_agent = f"{__title__}/{__version__} ({app_settings.ESI_USER_CONTACT_EMAIL}; +{__url__})"
|
|
494
|
+
else:
|
|
495
|
+
# AppName/1.2.3 (email@example.com) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi)
|
|
496
|
+
# or AppName/1.2.3 (email@example.com; +https://gitlab.com/) Django-ESI/1.2.3 (+https://gitlab.com/allianceauth/django-esi) (+https://gitlab.com/allianceauth/django-esi)
|
|
497
|
+
# Preferred
|
|
498
|
+
user_agent = f"{ua_appname}/{ua_version} ({app_settings.ESI_USER_CONTACT_EMAIL}{f'; +{ua_url})' if ua_url else ')'} {__title__}/{__version__} (+{__url__})"
|
|
478
499
|
|
|
479
500
|
client.user_agent = user_agent
|
|
480
501
|
|
|
@@ -535,10 +556,9 @@ class EsiClientProvider:
|
|
|
535
556
|
datasource: Name of the ESI datasource to access.
|
|
536
557
|
spec_file: Absolute path to a swagger spec file to load.
|
|
537
558
|
version: Base ESI API version. Accepted values are 'legacy', 'latest',
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
Note that spaces are used as delimiter.
|
|
559
|
+
ua_appname: Name of the App for generating a User-Agent,
|
|
560
|
+
ua_version: Version of the App for generating a User-Agent,
|
|
561
|
+
ua_url: (optional) URL To the Source Code or Documentation for generating a User-Agent,
|
|
542
562
|
kwargs: Explicit resource versions to build, in the form Character='v4'. \
|
|
543
563
|
Same values accepted as version.
|
|
544
564
|
|
|
@@ -550,30 +570,40 @@ class EsiClientProvider:
|
|
|
550
570
|
_client = None
|
|
551
571
|
|
|
552
572
|
def __init__(
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
version=None,
|
|
557
|
-
app_info_text=None,
|
|
558
|
-
**kwargs
|
|
559
|
-
):
|
|
573
|
+
self, datasource=None, spec_file=None, version=None,
|
|
574
|
+
app_info_text=None, # Deprecate in favour of the following variables
|
|
575
|
+
ua_appname: str = None, ua_version: str = None, ua_url: str = None, **kwargs) -> None:
|
|
560
576
|
self._datasource = datasource
|
|
561
577
|
self._spec_file = spec_file
|
|
562
578
|
self._version = version
|
|
563
|
-
self._app_text = app_info_text
|
|
579
|
+
self._app_text = app_info_text # Deprecate in favour of the following variables
|
|
580
|
+
self._ua_appname = ua_appname
|
|
581
|
+
self._ua_version = ua_version
|
|
582
|
+
self._ua_url = ua_url
|
|
564
583
|
self._kwargs = kwargs
|
|
565
584
|
|
|
585
|
+
if app_info_text is not None:
|
|
586
|
+
warnings.warn(
|
|
587
|
+
"The 'app_info_text' parameter is deprecated and will be removed in a future release. "
|
|
588
|
+
"Use 'ua_appname', 'ua_version', and `ua_url` to dynamically build a User-Agent instead",
|
|
589
|
+
DeprecationWarning,
|
|
590
|
+
stacklevel=2
|
|
591
|
+
)
|
|
592
|
+
|
|
566
593
|
@property
|
|
567
|
-
def client(self):
|
|
594
|
+
def client(self) -> SwaggerClient:
|
|
568
595
|
if self._client is None:
|
|
569
596
|
self._client = esi_client_factory(
|
|
570
597
|
datasource=self._datasource,
|
|
571
598
|
spec_file=self._spec_file,
|
|
572
599
|
version=self._version,
|
|
573
|
-
app_info_text=self._app_text,
|
|
600
|
+
app_info_text=self._app_text, # Deprecate in favour of the following variables
|
|
601
|
+
ua_appname=self._ua_appname,
|
|
602
|
+
ua_version=self._ua_version,
|
|
603
|
+
ua_url=self._ua_url,
|
|
574
604
|
**self._kwargs,
|
|
575
605
|
)
|
|
576
606
|
return self._client
|
|
577
607
|
|
|
578
|
-
def __str__(self):
|
|
608
|
+
def __str__(self) -> str:
|
|
579
609
|
return 'EsiClientProvider'
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import logging
|
|
2
2
|
from functools import wraps
|
|
3
|
+
from typing import Union
|
|
3
4
|
|
|
4
5
|
from .models import Token, CallbackRedirect
|
|
5
6
|
|
|
@@ -7,7 +8,7 @@ from .models import Token, CallbackRedirect
|
|
|
7
8
|
logger = logging.getLogger(__name__)
|
|
8
9
|
|
|
9
10
|
|
|
10
|
-
def _check_callback(request):
|
|
11
|
+
def _check_callback(request) -> Union[Token, None]:
|
|
11
12
|
# ensure session installed in database
|
|
12
13
|
if not request.session.exists(request.session.session_key):
|
|
13
14
|
logger.debug("Creating new session for %s", request.user)
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# Generated by Django 4.2 on 2025-04-04 01:28
|
|
2
|
+
|
|
3
|
+
from django.conf import settings
|
|
4
|
+
from django.db import migrations, models
|
|
5
|
+
import django.db.models.deletion
|
|
6
|
+
|
|
7
|
+
# Squashmigration imported a RunPython, not needed
|
|
8
|
+
# Manually optimized
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class Migration(migrations.Migration):
|
|
12
|
+
|
|
13
|
+
replaces = [('esi', '0001_initial'), ('esi', '0002_scopes_20161208'), ('esi', '0003_hide_tokens_from_admin_site'), ('esi', '0004_remove_unique_access_token'), ('esi', '0005_remove_token_length_limit'), ('esi', '0006_remove_url_length_limit'), ('esi', '0007_fix_mysql_8_migration'), ('esi', '0008_nullable_refresh_token'), ('esi', '0009_set_old_tokens_to_sso_v1'), ('esi', '0010_set_new_tokens_to_sso_v2'), ('esi', '0011_add_token_indices'), ('esi', '0012_fix_token_type_choices')]
|
|
14
|
+
|
|
15
|
+
initial = True
|
|
16
|
+
|
|
17
|
+
dependencies = [
|
|
18
|
+
migrations.swappable_dependency(settings.AUTH_USER_MODEL),
|
|
19
|
+
]
|
|
20
|
+
|
|
21
|
+
operations = [
|
|
22
|
+
migrations.CreateModel(
|
|
23
|
+
name='Scope',
|
|
24
|
+
fields=[
|
|
25
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
26
|
+
('name', models.CharField(help_text='The official EVE name for the scope.', max_length=100, unique=True)),
|
|
27
|
+
('help_text', models.TextField(help_text='The official EVE description of the scope.')),
|
|
28
|
+
],
|
|
29
|
+
),
|
|
30
|
+
migrations.CreateModel(
|
|
31
|
+
name='Token',
|
|
32
|
+
fields=[
|
|
33
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
34
|
+
('created', models.DateTimeField(auto_now_add=True)),
|
|
35
|
+
('access_token', models.TextField(editable=False, help_text='The access token granted by SSO.')),
|
|
36
|
+
('refresh_token', models.TextField(editable=False, help_text='A re-usable token to generate new access tokens upon expiry.', null=True)),
|
|
37
|
+
('character_id', models.IntegerField(db_index=True, help_text='The ID of the EVE character who authenticated by SSO.')),
|
|
38
|
+
('character_name', models.CharField(db_index=True, help_text='The name of the EVE character who authenticated by SSO.', max_length=100)),
|
|
39
|
+
('token_type', models.CharField(choices=[('character', 'Character'), ('corporation', 'Corporation')], default='character', help_text='The applicable range of the token.', max_length=100)),
|
|
40
|
+
('character_owner_hash', models.CharField(db_index=True, help_text='The unique string identifying this character and its owning EVE account. Changes if the owning account changes.', max_length=254)),
|
|
41
|
+
('scopes', models.ManyToManyField(blank=True, help_text='The access scopes granted by this token.', to='esi.scope')),
|
|
42
|
+
('user', models.ForeignKey(blank=True, help_text='The user to whom this token belongs.', null=True, on_delete=django.db.models.deletion.CASCADE, to=settings.AUTH_USER_MODEL)),
|
|
43
|
+
('sso_version', models.IntegerField(default=2, help_text='EVE SSO Version.')),
|
|
44
|
+
],
|
|
45
|
+
),
|
|
46
|
+
migrations.CreateModel(
|
|
47
|
+
name='CallbackRedirect',
|
|
48
|
+
fields=[
|
|
49
|
+
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
|
50
|
+
('url', models.TextField(default='/', help_text='The internal URL to redirect this callback towards.')),
|
|
51
|
+
('session_key', models.CharField(help_text='Session key identifying the session this redirect was created for.', max_length=254, unique=True)),
|
|
52
|
+
('state', models.CharField(help_text='OAuth2 state string representing this session.', max_length=128)),
|
|
53
|
+
('created', models.DateTimeField(auto_now_add=True)),
|
|
54
|
+
('token', models.ForeignKey(blank=True, help_text='Token generated by a completed code exchange from callback processing.', null=True, on_delete=django.db.models.deletion.CASCADE, to='esi.token')),
|
|
55
|
+
],
|
|
56
|
+
),
|
|
57
|
+
]
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import datetime
|
|
2
2
|
import re
|
|
3
3
|
import logging
|
|
4
|
+
from typing import ClassVar
|
|
4
5
|
|
|
5
6
|
from bravado.client import SwaggerClient
|
|
6
7
|
from requests.auth import HTTPBasicAuth
|
|
@@ -54,7 +55,7 @@ class Scope(models.Model):
|
|
|
54
55
|
except IndexError:
|
|
55
56
|
return name
|
|
56
57
|
|
|
57
|
-
def __str__(self):
|
|
58
|
+
def __str__(self) -> str:
|
|
58
59
|
return self.name
|
|
59
60
|
|
|
60
61
|
|
|
@@ -121,16 +122,16 @@ class Token(models.Model):
|
|
|
121
122
|
default=2
|
|
122
123
|
)
|
|
123
124
|
|
|
124
|
-
objects = TokenManager()
|
|
125
|
+
objects: ClassVar[TokenManager] = TokenManager()
|
|
125
126
|
|
|
126
|
-
def __str__(self):
|
|
127
|
+
def __str__(self) -> str:
|
|
127
128
|
try:
|
|
128
129
|
scopes = sorted(s.name for s in self.scopes.all())
|
|
129
130
|
except ValueError:
|
|
130
131
|
scopes = []
|
|
131
132
|
return f'{self.character_name} - {", ".join(scopes)}'
|
|
132
133
|
|
|
133
|
-
def __repr__(self):
|
|
134
|
+
def __repr__(self) -> str:
|
|
134
135
|
return "<{}(id={}): {}, {}>".format(
|
|
135
136
|
self.__class__.__name__,
|
|
136
137
|
self.pk,
|
|
@@ -248,7 +249,7 @@ class Token(models.Model):
|
|
|
248
249
|
logger.debug("Not a refreshable token.")
|
|
249
250
|
raise NotRefreshableTokenError()
|
|
250
251
|
|
|
251
|
-
def refresh_or_delete(self):
|
|
252
|
+
def refresh_or_delete(self) -> None:
|
|
252
253
|
"""Refresh this token or delete it if it can not be refreshed."""
|
|
253
254
|
try:
|
|
254
255
|
self.refresh()
|
|
@@ -345,10 +346,10 @@ class CallbackRedirect(models.Model):
|
|
|
345
346
|
)
|
|
346
347
|
)
|
|
347
348
|
|
|
348
|
-
def __str__(self):
|
|
349
|
+
def __str__(self) -> str:
|
|
349
350
|
return f"{self.session_key}: {self.url}"
|
|
350
351
|
|
|
351
|
-
def __repr__(self):
|
|
352
|
+
def __repr__(self) -> str:
|
|
352
353
|
return "<{}(pk={}): {} to {}>".format(
|
|
353
354
|
self.__class__.__name__, self.pk,
|
|
354
355
|
self.session_key, self.url
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from datetime import timedelta
|
|
2
2
|
import logging
|
|
3
|
+
from math import ceil
|
|
3
4
|
|
|
4
5
|
from celery import shared_task
|
|
5
6
|
|
|
@@ -26,14 +27,15 @@ def cleanup_callbackredirect(max_age=300):
|
|
|
26
27
|
|
|
27
28
|
|
|
28
29
|
@shared_task
|
|
29
|
-
def cleanup_token():
|
|
30
|
+
def cleanup_token() -> None:
|
|
30
31
|
"""
|
|
31
|
-
Delete expired :model:`esi.Token` models.
|
|
32
|
+
Delete Orphaned Tokens, then refresh or delete expired :model:`esi.Token` models.
|
|
32
33
|
"""
|
|
33
34
|
orphaned_tokens = Token.objects.filter(user__isnull=True)
|
|
34
35
|
if orphaned_tokens.exists():
|
|
35
36
|
logger.info("Deleting %d orphaned tokens.", orphaned_tokens.count())
|
|
36
37
|
orphaned_tokens.delete()
|
|
38
|
+
|
|
37
39
|
expired_tokens = Token.objects.exclude(user__isnull=True).get_expired()
|
|
38
40
|
if expired_tokens.exists():
|
|
39
41
|
logger.info(
|
|
@@ -46,6 +48,31 @@ def cleanup_token():
|
|
|
46
48
|
refresh_or_delete_token.apply_async(args=[token_pk], priority=8)
|
|
47
49
|
|
|
48
50
|
|
|
51
|
+
@shared_task
|
|
52
|
+
def cleanup_token_subset(fraction: int = 48) -> None:
|
|
53
|
+
"""
|
|
54
|
+
Delete Orphaned Tokens, then refresh or delete a subset of expired :model:`esi.Token` models.
|
|
55
|
+
|
|
56
|
+
This task operates on 1/fraction of the oldest tokens and can be called on a more regular schedule
|
|
57
|
+
"""
|
|
58
|
+
orphaned_tokens = Token.objects.filter(user__isnull=True)
|
|
59
|
+
if orphaned_tokens.exists():
|
|
60
|
+
logger.info("Deleting %d orphaned tokens.", orphaned_tokens.count())
|
|
61
|
+
orphaned_tokens.delete()
|
|
62
|
+
|
|
63
|
+
expired_tokens = Token.objects.exclude(user__isnull=True).get_expired()
|
|
64
|
+
expired_tokens_subset = expired_tokens.filter(
|
|
65
|
+
refresh_token__isnull=False
|
|
66
|
+
).order_by("created")[:ceil(expired_tokens.count() / fraction)]
|
|
67
|
+
|
|
68
|
+
if expired_tokens.exists():
|
|
69
|
+
logger.info(
|
|
70
|
+
f"Triggering bulk refresh of subset/possible {expired_tokens_subset.count()}/{expired_tokens.count()} expired tokens."
|
|
71
|
+
)
|
|
72
|
+
for token_pk in expired_tokens_subset.values_list("pk", flat=True):
|
|
73
|
+
refresh_or_delete_token.apply_async(args=[token_pk], priority=8)
|
|
74
|
+
|
|
75
|
+
|
|
49
76
|
@shared_task
|
|
50
77
|
def refresh_or_delete_token(token_pk: int):
|
|
51
78
|
token = Token.objects.get(pk=token_pk)
|
|
@@ -10,7 +10,7 @@
|
|
|
10
10
|
<meta name="description" content="">
|
|
11
11
|
<meta name="author" content="">
|
|
12
12
|
|
|
13
|
-
<title>{%
|
|
13
|
+
<title>{% translate "ESI Token Selection" %}</title>
|
|
14
14
|
|
|
15
15
|
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.6.1/css/bootstrap.min.css" integrity="sha512-T584yQ/tdRR5QwOpfvDfVQUidzfgc2339Lc8uBDtcp/wYu80d7jwBgAxbyMh0a9YM9F8N3tdErpFI8iaGx6x5g==" crossorigin="anonymous" referrerpolicy="no-referrer" />
|
|
16
16
|
<script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.6.0/jquery.slim.min.js" integrity="sha512-6ORWJX/LrnSjBzwefdNUyLCMTIsGoNP6NftMy2UAm1JBm6PRZCO1d7OHBStWpVFZLO+RerTvqX/Z9mBFfCJZ4A==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
|
@@ -41,11 +41,11 @@
|
|
|
41
41
|
</head>
|
|
42
42
|
<body class="bg-dark">
|
|
43
43
|
<div class="container-fluid mt-5">
|
|
44
|
-
<h3 class="text-light font-weight-light text-center">{%
|
|
44
|
+
<h3 class="text-light font-weight-light text-center">{% translate "Select Character" %}</h3><br>
|
|
45
45
|
<div class="mt-2">
|
|
46
46
|
<div class="col-lg-10 offset-lg-1 col-md-12" style="padding-left: 0 !important; padding-right: 0 !important;">
|
|
47
47
|
<div class="card text-white bg-secondary">
|
|
48
|
-
<div class="card-header text-center">{%
|
|
48
|
+
<div class="card-header text-center">{% translate "Scopes Requested" %}</div>
|
|
49
49
|
<div class="card-body align-middle text-center">
|
|
50
50
|
{% for scope in scopes %}<span class="badge badge-dark">{{ scope|scope_friendly_name }}</span> {% endfor %}
|
|
51
51
|
</div>
|
|
@@ -58,7 +58,7 @@
|
|
|
58
58
|
{% if tokens|length > 14 %}
|
|
59
59
|
<form method="post">
|
|
60
60
|
<div class="card text-white bg-secondary m-2 sso-card">
|
|
61
|
-
<div class="card-header text-center">{%
|
|
61
|
+
<div class="card-header text-center">{% translate "New Character" %}</div>
|
|
62
62
|
<div class="card-body text-center d-none d-sm-block">
|
|
63
63
|
<input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
|
|
64
64
|
src="https://images.evetech.net/characters/1/portrait?size=256">
|
|
@@ -68,7 +68,7 @@
|
|
|
68
68
|
<input type="hidden" name="_add" value="True">
|
|
69
69
|
<a href="#" type="submit" class="m-1">
|
|
70
70
|
<input type="image" name="go" src="{% static 'esi\img\EVE_SSO_Login_Buttons_Small_Black.png'%}"
|
|
71
|
-
alt="{%
|
|
71
|
+
alt="{% translate "Add Token" %}">
|
|
72
72
|
</a>
|
|
73
73
|
</div>
|
|
74
74
|
</div>
|
|
@@ -85,8 +85,8 @@
|
|
|
85
85
|
</div>
|
|
86
86
|
<div class="card-footer">
|
|
87
87
|
<input type="hidden" name="_token" value="{{ token.pk }}">
|
|
88
|
-
<button class="btn btn-success w-100" type="submit" title="{%
|
|
89
|
-
{%
|
|
88
|
+
<button class="btn btn-success w-100" type="submit" title="{% translate "Select" %}" formmethod="post">
|
|
89
|
+
{% translate "Select" %}
|
|
90
90
|
</button>
|
|
91
91
|
</div>
|
|
92
92
|
</div>
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
<form method="post">
|
|
96
96
|
{% csrf_token %}
|
|
97
97
|
<div class="card text-white bg-secondary m-2 sso-card">
|
|
98
|
-
<div class="card-header text-center">{%
|
|
98
|
+
<div class="card-header text-center">{% translate "New Character" %}</div>
|
|
99
99
|
<div class="card-body text-center d-none d-sm-block">
|
|
100
100
|
<input type="image" formmethod="post" class="ra-avatar img-responsive character-image rounded-lg"
|
|
101
101
|
src="https://images.evetech.net/characters/1/portrait?size=256">
|
|
@@ -104,7 +104,7 @@
|
|
|
104
104
|
<input type="hidden" name="_add" value="True">
|
|
105
105
|
<a href="#" type="submit" class="m-1">
|
|
106
106
|
<input type="image" name="go" src="{% static 'esi\img\EVE_SSO_Login_Buttons_Small_Black.png'%}"
|
|
107
|
-
alt="{%
|
|
107
|
+
alt="{% translate "Add Token" %}">
|
|
108
108
|
</a>
|
|
109
109
|
</div>
|
|
110
110
|
</div>
|