From 1fc71a0738413dc6bfcf7f5c81648c39d64cba43 Mon Sep 17 00:00:00 2001 From: colcrunch Date: Thu, 22 Mar 2018 15:43:50 -0400 Subject: [PATCH 01/47] Fix celerybeat task in ts3 config. (#998) --- docs/installation/services/teamspeak3.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/services/teamspeak3.md b/docs/installation/services/teamspeak3.md index a10c761e..aec183eb 100644 --- a/docs/installation/services/teamspeak3.md +++ b/docs/installation/services/teamspeak3.md @@ -23,7 +23,7 @@ In your auth project's settings file, do the following: TEAMSPEAK3_PUBLIC_URL = '' CELERYBEAT_SCHEDULE['run_ts3_group_update'] = { - 'task': 'services.modules.teamspeak3.tasks.run_ts3_group_update', + 'task': 'allianceauth.services.modules.teamspeak3.tasks.run_ts3_group_update', 'schedule': crontab(minute='*/30'), } From bdb3ab366fba2cd7e1ae799b5b75e46c63d95bf3 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Thu, 22 Mar 2018 17:59:24 -0400 Subject: [PATCH 02/47] Group list API endpoint has moved. Allow infinite group cache age. Thanks @TargetZ3R0 --- allianceauth/services/modules/discourse/manager.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/allianceauth/services/modules/discourse/manager.py b/allianceauth/services/modules/discourse/manager.py index 1d98475d..d36f20c4 100644 --- a/allianceauth/services/modules/discourse/manager.py +++ b/allianceauth/services/modules/discourse/manager.py @@ -7,7 +7,7 @@ from hashlib import md5 logger = logging.getLogger(__name__) -GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # default 2 hours +GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # default 2 hours class DiscourseError(Exception): @@ -23,7 +23,7 @@ class DiscourseError(Exception): ENDPOINTS = { 'groups': { 'list': { - 'path': "/admin/groups.json", + 'path': "/groups/search.json", 'method': 'get', 'args': { 'required': [], From cf7ddbe0e12fd05f1cf80266f1c1c36764a0a599 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Fri, 23 Mar 2018 11:16:59 -0400 Subject: [PATCH 03/47] Set hostname to domain, not localhost. Stop using sudo commands. Trust the user to handle permissions. Closes #994 --- docs/installation/services/discourse.md | 32 ++++++++++--------------- 1 file changed, 13 insertions(+), 19 deletions(-) diff --git a/docs/installation/services/discourse.md b/docs/installation/services/discourse.md index 54fd38cd..39ead8de 100644 --- a/docs/installation/services/discourse.md +++ b/docs/installation/services/discourse.md @@ -17,31 +17,25 @@ In your auth project's settings file, do the following: wget -qO- https://get.docker.io/ | sh -### Get docker permissions - - sudo usermod -aG docker allianceserver - -Logout, then back in for changes to take effect. - ## Install Discourse ### Download Discourse - sudo mkdir /var/discourse - sudo git clone https://github.com/discourse/discourse_docker.git /var/discourse + mkdir /var/discourse + git clone https://github.com/discourse/discourse_docker.git /var/discourse ### Configure cd /var/discourse - sudo cp samples/standalone.yml containers/app.yml - sudo nano containers/app.yml + cp samples/standalone.yml containers/app.yml + nano containers/app.yml Change the following: - - `DISCOURSE_DEVELOPER_EMAILS` should be a list of admin account email addresses separated by commas - - `DISCOUSE_HOSTNAME` should be 127.0.0.1 - - Everything with `SMTP` depends on your mail settings. Account created through auth do not require email validation, so to ignore everything email (NOT RECOMMENDED), just change the SMTP address to something random so it'll install. Note that not setting up email means any password resets emails won't be sent, and auth cannot reset these. [There are plenty of free email services online recommended by Discourse.](https://github.com/discourse/discourse/blob/master/docs/INSTALL-email.md#recommended-email-providers-for-discourse) + - `DISCOURSE_DEVELOPER_EMAILS` should be a list of admin account email addresses separated by commas. + - `DISCOUSE_HOSTNAME` should be `discourse.example.com` or something similar. + - Everything with `SMTP` depends on your mail settings. [There are plenty of free email services online recommended by Discourse](https://github.com/discourse/discourse/blob/master/docs/INSTALL-email.md#recommended-email-providers-for-discourse) if you haven't set one up for auth already. -To install behind apache, look for this secion: +To install behind apache/nginx, look for this section: ... ## which TCP/IP ports should this container expose? @@ -61,7 +55,7 @@ Or any other port will do, if taken. Remember this number. ### Build and launch - sudo nano /etc/default/docker + nano /etc/default/docker Uncomment this line: @@ -69,12 +63,12 @@ Uncomment this line: Restart docker: - sudo service docker restart + service docker restart Now build: - sudo ./launcher bootstrap app - sudo ./launcher start app + ./launcher bootstrap app + ./launcher start app ## Web Server Configuration @@ -103,7 +97,7 @@ A minimal nginx config might look like: ### Generate admin account -From the /var/discourse folder, +From the `/var/discourse` directory, ./launcher enter app rake admin:create From 826666185539901acc0e9c6fa0cb077fcd43fc4d Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 2 Apr 2018 20:38:12 -0400 Subject: [PATCH 04/47] Sanitize username on Discord user join. Thanks @iakopo --- allianceauth/services/modules/discord/manager.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allianceauth/services/modules/discord/manager.py b/allianceauth/services/modules/discord/manager.py index 08e1d1f5..7f28e6c7 100644 --- a/allianceauth/services/modules/discord/manager.py +++ b/allianceauth/services/modules/discord/manager.py @@ -204,7 +204,7 @@ class DiscordOAuthManager: 'access_token': token, } if nickname: - data['nick'] = nickname + data['nick'] = DiscordOAuthManager._sanitize_name(nickname) custom_headers['authorization'] = 'Bot ' + settings.DISCORD_BOT_TOKEN r = requests.put(path, headers=custom_headers, json=data) logger.debug("Got status code %s after joining Discord server" % r.status_code) From e694921fe6c38c1cd2d0a6dd59ccbe06eb786757 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 3 Apr 2018 13:57:26 -0400 Subject: [PATCH 05/47] Include mandatory DB package notice. Thanks @zuiji --- docs/installation/auth/allianceauth.md | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index bff50932..ab453efc 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -41,6 +41,11 @@ CentOS: yum install mariadb-server mariadb-devel mariadb +```eval_rst +.. note:: + If you don't plan on running the database on the same server as auth you still need to install the libmysqlclient-dev package on Ubuntu or mariadb-devel package on CentOS. +``` + ### Redis and Other Tools A few extra utilities are also required for installation of packages. From cb46ecb00247b08c94c591ff79537a2396469ded Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 3 Apr 2018 15:11:05 -0400 Subject: [PATCH 06/47] Correct mysql packages to mariadb on Ubuntu Thanks @kormat --- docs/installation/auth/allianceauth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index ab453efc..52f658bb 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -35,7 +35,7 @@ It's recommended to use a database service instead of sqlite. Many options are a Ubuntu: - apt-get install mariadb-server mysql-client libmysqlclient-dev + apt-get install mariadb-server mariadb-client libmariadbclient-dev CentOS: From 4eb6b73903bc8ff2dd3eccc22559c5abd039a0fe Mon Sep 17 00:00:00 2001 From: randomic Date: Wed, 4 Apr 2018 00:25:47 +0100 Subject: [PATCH 07/47] Nameformat configs which default to corp where alliance is None (#1003) Add nameconfig format for alliance_or_corp_ticker Add nameconfig format for alliance_or_corp_name Update docs for new nameformats Correct missing dict key if no alliance. --- allianceauth/services/hooks.py | 5 ++++- docs/features/nameformats.md | 2 ++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/allianceauth/services/hooks.py b/allianceauth/services/hooks.py index 6318fdf9..7eae2371 100644 --- a/allianceauth/services/hooks.py +++ b/allianceauth/services/hooks.py @@ -160,6 +160,7 @@ class NameFormatter: 'corp_id': getattr(main_char, 'corporation_id', None), 'alliance_name': getattr(main_char, 'alliance_name', None), 'alliance_id': getattr(main_char, 'alliance_id', None), + 'alliance_ticker': None, 'username': self.user.username, } @@ -168,7 +169,9 @@ class NameFormatter: try: format_data['alliance_ticker'] = getattr(getattr(main_char, 'alliance', None), 'alliance_ticker', None) except ObjectDoesNotExist: - format_data['alliance_ticker'] = None + pass + format_data['alliance_or_corp_name'] = format_data['alliance_name'] or format_data['corp_name'] + format_data['alliance_or_corp_ticker'] = format_data['alliance_ticker'] or format_data['corp_ticker'] return format_data @cached_property diff --git a/docs/features/nameformats.md b/docs/features/nameformats.md index 707f710a..e26837a9 100644 --- a/docs/features/nameformats.md +++ b/docs/features/nameformats.md @@ -53,6 +53,8 @@ The following fields are available from a users account and main character: - `alliance_id` - `alliance_name` - `alliance_ticker` + - `alliance_or_corp_name` (defaults to corp name if there is no alliance) + - `alliance_or_corp_ticker` (defaults to corp ticker if there is no alliance) ## Building a formatter string From 7767226000a6486862ac1c59437bf6b782f9c62d Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 3 Apr 2018 21:03:42 -0400 Subject: [PATCH 08/47] Still collect emails from newly registered users. Log in users immediately if no validation required. Document new optional setting in project template settings file. --- allianceauth/authentication/views.py | 21 +++++++++++++++---- .../project_name/settings/local.py | 4 +++- 2 files changed, 20 insertions(+), 5 deletions(-) diff --git a/allianceauth/authentication/views.py b/allianceauth/authentication/views.py index e48cec8b..9ddd2f85 100644 --- a/allianceauth/authentication/views.py +++ b/allianceauth/authentication/views.py @@ -95,20 +95,33 @@ class RegistrationView(BaseRegistrationView): form_class = RegistrationForm success_url = 'authentication:dashboard' - def dispatch(self, *args, **kwargs): + def get_success_url(self, user): + if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True): + return 'authentication:dashboard', (), {} + return super().get_success_url(user) + + def dispatch(self, request, *args, **kwargs): # We're storing a key in the session to pass user information from OAuth response. Make sure it's there. if not self.request.session.get('registration_uid', None) or not User.objects.filter( pk=self.request.session.get('registration_uid')).exists(): messages.error(self.request, _('Registration token has expired.')) return redirect(settings.LOGIN_URL) - return super(RegistrationView, self).dispatch(*args, **kwargs) + if not getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True): + # Keep the request so the user can be automagically logged in. + setattr(self, 'request', request) + return super(RegistrationView, self).dispatch(request, *args, **kwargs) def register(self, form): user = User.objects.get(pk=self.request.session.get('registration_uid')) user.email = form.cleaned_data['email'] user_registered.send(self.__class__, user=user, request=self.request) - # Go to Step 3 - self.send_activation_email(user) + if getattr(settings, 'REGISTRATION_VERIFY_EMAIL', True): + # Go to Step 3 + self.send_activation_email(user) + else: + user.is_active = True + user.save() + login(self.request, user, 'allianceauth.authentication.backends.StateBackend') return user def get_activation_key(self, user): diff --git a/allianceauth/project_template/project_name/settings/local.py b/allianceauth/project_template/project_name/settings/local.py index ac93427f..039feebd 100644 --- a/allianceauth/project_template/project_name/settings/local.py +++ b/allianceauth/project_template/project_name/settings/local.py @@ -41,10 +41,12 @@ ESI_SSO_CLIENT_ID = '' ESI_SSO_CLIENT_SECRET = '' ESI_SSO_CALLBACK_URL = '' -# Emails are validated before new users can log in. +# By default emails are validated before new users can log in. # It's recommended to use a free service like SparkPost or Mailgun to send email. # https://www.sparkpost.com/docs/integrations/django/ # Set the default from email to something like 'noreply@example.com' +# Email validation can be turned off by uncommenting the line below. This can break some services. +# REGISTRATION_VERIFY_EMAIL = False EMAIL_HOST = '' EMAIL_PORT = 587 EMAIL_HOST_USER = '' From 6fa788a8f9ec3d504a0abd5f8d11bf9da3b374f7 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 7 Apr 2018 13:39:20 -0400 Subject: [PATCH 09/47] Use libmysqlclient-dev on Ubuntu `libmariadbclient-dev` is unavailable on Xenial (and the suggested replacement `libmariadb-client-lgpl-dev-compat` doesn't have the `mysql_config` symlink patch for whatever reason). https://bugs.launchpad.net/ubuntu/+source/mariadb-client-lgpl/+bug/1575968 https://bugs.launchpad.net/ubuntu/+source/mariadb-client-lgpl/+bug/1546923 https://anonscm.debian.org/cgit/pkg-mysql/mariadb-client-lgpl.git/commit/debian/libmariadb-dev-compat.links?id=0bbbb8ea0bbeab4a6ebb1e62b92c1ca347061be4 Thanks @kormat --- docs/installation/auth/allianceauth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index 52f658bb..44273ced 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -35,7 +35,7 @@ It's recommended to use a database service instead of sqlite. Many options are a Ubuntu: - apt-get install mariadb-server mariadb-client libmariadbclient-dev + apt-get install mariadb-server mariadb-client libmysqlclient-dev CentOS: From b53c7a624bae487d251b01ddf50bd201ab2fb95c Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 27 Mar 2018 08:23:21 -0400 Subject: [PATCH 10/47] Use queryset delete to purge non-refreshable tokens. --- .../authentication/migrations/0015_user_profiles.py | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/allianceauth/authentication/migrations/0015_user_profiles.py b/allianceauth/authentication/migrations/0015_user_profiles.py index 7ba696b6..592ba0e8 100644 --- a/allianceauth/authentication/migrations/0015_user_profiles.py +++ b/allianceauth/authentication/migrations/0015_user_profiles.py @@ -96,6 +96,11 @@ def create_blue_group(apps, schema_editor): pass +def purge_tokens(apps, schema_editor): + Token = apps.get_model('esi', 'Token') + Token.objects.filter(refresh_token__isnull=True).delete() + + def populate_ownerships(apps, schema_editor): Token = apps.get_model('esi', 'Token') CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership') @@ -221,6 +226,7 @@ class Migration(migrations.Migration): migrations.RunPython(create_guest_state, migrations.RunPython.noop), migrations.RunPython(create_member_state, create_member_group), migrations.RunPython(create_blue_state, create_blue_group), + migrations.RunPython(purge_tokens, migrations.RunPython.noop), migrations.RunPython(populate_ownerships, migrations.RunPython.noop), migrations.RunPython(create_profiles, recreate_authservicesinfo), migrations.RemoveField( From 106f6bbcea6415600142d1b64fb274f8a8217d01 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Thu, 22 Mar 2018 10:37:49 -0400 Subject: [PATCH 11/47] Fix test user creation. --- allianceauth/eveonline/autogroups/models.py | 4 +++- allianceauth/eveonline/autogroups/signals.py | 4 +--- allianceauth/eveonline/autogroups/tests/__init__.py | 5 +++-- .../eveonline/autogroups/tests/test_models.py | 4 ++-- .../eveonline/autogroups/tests/test_signals.py | 12 ++++++------ 5 files changed, 15 insertions(+), 14 deletions(-) diff --git a/allianceauth/eveonline/autogroups/models.py b/allianceauth/eveonline/autogroups/models.py index 8dd78f58..2a293b70 100644 --- a/allianceauth/eveonline/autogroups/models.py +++ b/allianceauth/eveonline/autogroups/models.py @@ -11,7 +11,7 @@ logger = logging.getLogger(__name__) def get_users_for_state(state: State): return User.objects.select_related('profile').prefetch_related('profile__main_character')\ - .filter(profile__state__pk=state.pk) + .filter(profile__state_id=state.pk) class AutogroupsConfigManager(models.Manager): @@ -36,6 +36,8 @@ class AutogroupsConfigManager(models.Manager): :param state: State to update user for :return: """ + import traceback + print(traceback.print_stack()) if state is None: state = user.profile.state for config in self.filter(states=state): diff --git a/allianceauth/eveonline/autogroups/signals.py b/allianceauth/eveonline/autogroups/signals.py index 4fcfdeeb..85d18c5e 100644 --- a/allianceauth/eveonline/autogroups/signals.py +++ b/allianceauth/eveonline/autogroups/signals.py @@ -45,9 +45,7 @@ def check_groups_on_profile_update(sender, instance, created, *args, **kwargs): """ Trigger check when main character or state changes. """ - update_fields = kwargs.pop('update_fields', []) or [] - if 'main_character' in update_fields or 'state' in update_fields: - AutogroupsConfig.objects.update_groups_for_user(instance.user) + AutogroupsConfig.objects.update_groups_for_user(instance.user) @receiver(m2m_changed, sender=AutogroupsConfig.states.through) diff --git a/allianceauth/eveonline/autogroups/tests/__init__.py b/allianceauth/eveonline/autogroups/tests/__init__.py index d46d9d35..51704c12 100644 --- a/allianceauth/eveonline/autogroups/tests/__init__.py +++ b/allianceauth/eveonline/autogroups/tests/__init__.py @@ -1,8 +1,7 @@ from unittest import mock from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed from allianceauth.authentication.models import UserProfile -from allianceauth.authentication.signals import state_changed -from allianceauth.eveonline.models import EveCharacter +from allianceauth.authentication.signals import reassess_on_profile_save from .. import signals from ..models import AutogroupsConfig @@ -14,6 +13,7 @@ def patch(target, *args, **kwargs): def connect_signals(): + post_save.connect(receiver=reassess_on_profile_save, sender=UserProfile) pre_save.connect(receiver=signals.pre_save_config, sender=AutogroupsConfig) pre_delete.connect(receiver=signals.pre_delete_config, sender=AutogroupsConfig) post_save.connect(receiver=signals.check_groups_on_profile_update, sender=UserProfile) @@ -21,6 +21,7 @@ def connect_signals(): def disconnect_signals(): + post_save.disconnect(receiver=reassess_on_profile_save, sender=UserProfile) pre_save.disconnect(receiver=signals.pre_save_config, sender=AutogroupsConfig) pre_delete.disconnect(receiver=signals.pre_delete_config, sender=AutogroupsConfig) post_save.disconnect(receiver=signals.check_groups_on_profile_update, sender=UserProfile) diff --git a/allianceauth/eveonline/autogroups/tests/test_models.py b/allianceauth/eveonline/autogroups/tests/test_models.py index b59bd237..7fd9b63f 100644 --- a/allianceauth/eveonline/autogroups/tests/test_models.py +++ b/allianceauth/eveonline/autogroups/tests/test_models.py @@ -16,8 +16,6 @@ class AutogroupsConfigTestCase(TestCase): # Disconnect signals disconnect_signals() - self.member = AuthUtils.create_member('test user') - state = AuthUtils.get_member_state() self.alliance = EveAllianceInfo.objects.create( @@ -38,6 +36,8 @@ class AutogroupsConfigTestCase(TestCase): state.member_alliances.add(self.alliance) state.member_corporations.add(self.corp) + self.member = AuthUtils.create_member('test user') + def tearDown(self): # Reconnect signals connect_signals() diff --git a/allianceauth/eveonline/autogroups/tests/test_signals.py b/allianceauth/eveonline/autogroups/tests/test_signals.py index d89be966..e7f1f2a9 100644 --- a/allianceauth/eveonline/autogroups/tests/test_signals.py +++ b/allianceauth/eveonline/autogroups/tests/test_signals.py @@ -13,8 +13,6 @@ from . import patch, disconnect_signals, connect_signals class SignalsTestCase(TestCase): def setUp(self): disconnect_signals() - self.member = AuthUtils.create_member('test user') - state = AuthUtils.get_member_state() self.char = EveCharacter.objects.create( @@ -27,9 +25,6 @@ class SignalsTestCase(TestCase): alliance_name='alliance name', ) - self.member.profile.main_character = self.char - self.member.profile.save() - self.alliance = EveAllianceInfo.objects.create( alliance_id='3456', alliance_name='alliance name', @@ -47,6 +42,11 @@ class SignalsTestCase(TestCase): state.member_alliances.add(self.alliance) state.member_corporations.add(self.corp) + + self.member = AuthUtils.create_member('test user') + self.member.profile.main_character = self.char + self.member.profile.save() + connect_signals() @patch('.models.AutogroupsConfigManager.update_groups_for_user') @@ -71,10 +71,10 @@ class SignalsTestCase(TestCase): alliance_id='3456', alliance_name='alliance name', ) + # Trigger signal self.member.profile.main_character = char self.member.profile.save() - self.assertTrue(update_groups_for_user.called) self.assertEqual(update_groups_for_user.call_count, 1) args, kwargs = update_groups_for_user.call_args From 89e57400275d76357b594b60aa6147d60448f251 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Fri, 23 Mar 2018 11:53:24 -0400 Subject: [PATCH 12/47] Update autogroups on main character save Closes #997 --- allianceauth/eveonline/autogroups/models.py | 2 -- allianceauth/eveonline/autogroups/signals.py | 11 +++++++++++ .../eveonline/autogroups/tests/test_signals.py | 7 +++---- 3 files changed, 14 insertions(+), 6 deletions(-) diff --git a/allianceauth/eveonline/autogroups/models.py b/allianceauth/eveonline/autogroups/models.py index 2a293b70..4863ded9 100644 --- a/allianceauth/eveonline/autogroups/models.py +++ b/allianceauth/eveonline/autogroups/models.py @@ -36,8 +36,6 @@ class AutogroupsConfigManager(models.Manager): :param state: State to update user for :return: """ - import traceback - print(traceback.print_stack()) if state is None: state = user.profile.state for config in self.filter(states=state): diff --git a/allianceauth/eveonline/autogroups/signals.py b/allianceauth/eveonline/autogroups/signals.py index 85d18c5e..2ef7397b 100644 --- a/allianceauth/eveonline/autogroups/signals.py +++ b/allianceauth/eveonline/autogroups/signals.py @@ -2,6 +2,7 @@ import logging from django.dispatch import receiver from django.db.models.signals import pre_save, post_save, pre_delete, m2m_changed from allianceauth.authentication.models import UserProfile, State +from allianceauth.eveonline.models import EveCharacter from .models import AutogroupsConfig @@ -62,3 +63,13 @@ def autogroups_states_changed(sender, instance, action, reverse, model, pk_set, except State.DoesNotExist: # Deleted States handled by the profile state change pass + + +@receiver(post_save, sender=EveCharacter) +def check_groups_on_character_update(sender, instance, created, *args, **kwargs): + if not created: + try: + profile = UserProfile.objects.prefetch_related('user').get(main_character_id=instance.pk) + AutogroupsConfig.objects.update_groups_for_user(profile.user) + except UserProfile.DoesNotExist: + pass diff --git a/allianceauth/eveonline/autogroups/tests/test_signals.py b/allianceauth/eveonline/autogroups/tests/test_signals.py index e7f1f2a9..0ff3aa4c 100644 --- a/allianceauth/eveonline/autogroups/tests/test_signals.py +++ b/allianceauth/eveonline/autogroups/tests/test_signals.py @@ -1,11 +1,11 @@ from django.test import TestCase -from django.contrib.auth.models import Group, User +from django.contrib.auth.models import User from allianceauth.tests.auth_utils import AuthUtils from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo -from ..models import AutogroupsConfig, ManagedAllianceGroup +from ..models import AutogroupsConfig from . import patch, disconnect_signals, connect_signals @@ -52,8 +52,7 @@ class SignalsTestCase(TestCase): @patch('.models.AutogroupsConfigManager.update_groups_for_user') def test_check_groups_on_profile_update_state(self, update_groups_for_user): # Trigger signal - self.member.profile.state = AuthUtils.get_guest_state() - self.member.profile.save() + self.member.profile.assign_state(state=AuthUtils.get_guest_state()) self.assertTrue(update_groups_for_user.called) self.assertEqual(update_groups_for_user.call_count, 1) From ef9284030b4a2ccbc69c824c62b0ac7a0210cbaf Mon Sep 17 00:00:00 2001 From: Adarnof Date: Fri, 23 Mar 2018 12:03:50 -0400 Subject: [PATCH 13/47] Remove autogroups if no config for state. --- allianceauth/eveonline/autogroups/models.py | 10 +++++++-- .../autogroups/tests/test_managers.py | 22 ++++++++++++++++++- 2 files changed, 29 insertions(+), 3 deletions(-) diff --git a/allianceauth/eveonline/autogroups/models.py b/allianceauth/eveonline/autogroups/models.py index 4863ded9..7e7746c6 100644 --- a/allianceauth/eveonline/autogroups/models.py +++ b/allianceauth/eveonline/autogroups/models.py @@ -38,8 +38,14 @@ class AutogroupsConfigManager(models.Manager): """ if state is None: state = user.profile.state - for config in self.filter(states=state): - config.update_group_membership_for_user(user) + configs = self.filter(states=state) + if configs: + [config.update_group_membership_for_user(user) for config in configs] + else: + # No config for this user's state. Remove all managed groups. + for config in self.all(): + config.remove_user_from_alliance_groups(user) + config.remove_user_from_corp_groups(user) class AutogroupsConfig(models.Model): diff --git a/allianceauth/eveonline/autogroups/tests/test_managers.py b/allianceauth/eveonline/autogroups/tests/test_managers.py index dbdecfb2..49084938 100644 --- a/allianceauth/eveonline/autogroups/tests/test_managers.py +++ b/allianceauth/eveonline/autogroups/tests/test_managers.py @@ -7,7 +7,7 @@ from . import patch class AutogroupsConfigManagerTestCase(TestCase): - def test_update_groups_for_state(self, ): + def test_update_groups_for_state(self): member = AuthUtils.create_member('test member') obj = AutogroupsConfig.objects.create() obj.states.add(member.profile.state) @@ -32,3 +32,23 @@ class AutogroupsConfigManagerTestCase(TestCase): self.assertEqual(update_group_membership_for_user.call_count, 1) args, kwargs = update_group_membership_for_user.call_args self.assertEqual(args[0], member) + + @patch('.models.AutogroupsConfig.update_group_membership_for_user') + @patch('.models.AutogroupsConfig.remove_user_from_alliance_groups') + @patch('.models.AutogroupsConfig.remove_user_from_corp_groups') + def test_update_groups_no_config(self, remove_corp, remove_alliance, update_groups): + member = AuthUtils.create_member('test member') + obj = AutogroupsConfig.objects.create() + + # Corp and alliance groups should be removed from users if their state has no config + AutogroupsConfig.objects.update_groups_for_user(member) + + self.assertFalse(update_groups.called) + self.assertTrue(remove_alliance.called) + self.assertTrue(remove_corp.called) + + # The normal group assignment should occur if there state has a config + obj.states.add(member.profile.state) + AutogroupsConfig.objects.update_groups_for_user(member) + + self.assertTrue(update_groups.called) From ad1fd633b11e7a1ae36f331d3ec41cb6f416d953 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 7 Apr 2018 20:59:00 -0400 Subject: [PATCH 14/47] Ensure autogroups are removed if new state has config --- allianceauth/eveonline/autogroups/models.py | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/allianceauth/eveonline/autogroups/models.py b/allianceauth/eveonline/autogroups/models.py index 7e7746c6..8f5bbe9e 100644 --- a/allianceauth/eveonline/autogroups/models.py +++ b/allianceauth/eveonline/autogroups/models.py @@ -38,14 +38,13 @@ class AutogroupsConfigManager(models.Manager): """ if state is None: state = user.profile.state - configs = self.filter(states=state) - if configs: - [config.update_group_membership_for_user(user) for config in configs] - else: - # No config for this user's state. Remove all managed groups. - for config in self.all(): - config.remove_user_from_alliance_groups(user) - config.remove_user_from_corp_groups(user) + for config in self.filter(states=state): + # grant user new groups for their state + config.update_group_membership_for_user(user) + for config in self.exclude(states=state): + # ensure user does not have groups from previous state + config.remove_user_from_alliance_groups(user) + config.remove_user_from_corp_groups(user) class AutogroupsConfig(models.Model): From c725de7b5ba02bffeb5285569332bf42797224e7 Mon Sep 17 00:00:00 2001 From: Stephen Shirley Date: Sun, 8 Apr 2018 11:42:18 +0200 Subject: [PATCH 15/47] Remove trailing , from CELERYBEAT_SCHEDULE example. If a user copies the example verbatim, celery logs this error: ``` [2018-04-07 14:57:29,930: ERROR/MainProcess] Cannot add entry 'update_all_corpstats' to database schedule: TypeE rror('from_entry() argument after ** must be a mapping, not tuple',). Contents: ({'task': 'allianceauth.corputil s.tasks.update_all_corpstats', 'schedule': },) ``` --- docs/features/corpstats.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/features/corpstats.md b/docs/features/corpstats.md index 0a274826..18beb86b 100644 --- a/docs/features/corpstats.md +++ b/docs/features/corpstats.md @@ -116,7 +116,7 @@ By default Corp Stats are only updated on demand. If you want to automatically r CELERYBEAT_SCHEDULE['update_all_corpstats'] = { 'task': 'allianceauth.corputils.tasks.update_all_corpstats', 'schedule': crontab(minute=0, hour="*/6"), - }, + } Adjust the crontab as desired. From d6df5184a6b8e0df99331d5eebae4fc6fcbdf737 Mon Sep 17 00:00:00 2001 From: Ariel Rin Date: Mon, 9 Apr 2018 13:22:06 +1000 Subject: [PATCH 16/47] Set minute for Cron to stop mass task creation (#1010) --- allianceauth/project_template/project_name/settings/base.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/allianceauth/project_template/project_name/settings/base.py b/allianceauth/project_template/project_name/settings/base.py index 73a841f4..45889964 100644 --- a/allianceauth/project_template/project_name/settings/base.py +++ b/allianceauth/project_template/project_name/settings/base.py @@ -41,11 +41,11 @@ CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler" CELERYBEAT_SCHEDULE = { 'esi_cleanup_callbackredirect': { 'task': 'esi.tasks.cleanup_callbackredirect', - 'schedule': crontab(hour='*/4'), + 'schedule': crontab(minute=0, hour='*/4'), }, 'esi_cleanup_token': { 'task': 'esi.tasks.cleanup_token', - 'schedule': crontab(day_of_month='*/1'), + 'schedule': crontab(minute=0, hour=0), }, 'run_model_update': { 'task': 'allianceauth.eveonline.tasks.run_model_update', @@ -53,7 +53,7 @@ CELERYBEAT_SCHEDULE = { }, 'check_all_character_ownership': { 'task': 'allianceauth.authentication.tasks.check_all_character_ownership', - 'schedule': crontab(hour='*/4'), + 'schedule': crontab(minute=0, hour='*/4'), } } From 8a7389064632f5279cea5ece512a81b14113ba33 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 9 Apr 2018 21:53:41 -0400 Subject: [PATCH 17/47] Ensure ticker is fetched if alliance_or_corp used. Closes #1011 --- allianceauth/services/hooks.py | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/allianceauth/services/hooks.py b/allianceauth/services/hooks.py index 7eae2371..3e564bbe 100644 --- a/allianceauth/services/hooks.py +++ b/allianceauth/services/hooks.py @@ -4,6 +4,7 @@ from django.utils.functional import cached_property from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from string import Formatter +import re from allianceauth.hooks import get_hooks @@ -164,7 +165,7 @@ class NameFormatter: 'username': self.user.username, } - if main_char is not None and 'alliance_ticker' in self.string_formatter: + if main_char is not None and re.match('.*alliance(?:_or_corp)?_ticker', self.string_formatter): # Reduces db lookups try: format_data['alliance_ticker'] = getattr(getattr(main_char, 'alliance', None), 'alliance_ticker', None) From 933c12b48d7c931f39ea7c00519e475bdc2901a4 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 9 Apr 2018 22:14:21 -0400 Subject: [PATCH 18/47] Increase telnet timeout Should help tolerate slower responses from remote servers. Closes #751 Thanks @namenmalkav --- allianceauth/services/modules/teamspeak3/util/ts3.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allianceauth/services/modules/teamspeak3/util/ts3.py b/allianceauth/services/modules/teamspeak3/util/ts3.py index 5c13f9a6..ead9ea60 100755 --- a/allianceauth/services/modules/teamspeak3/util/ts3.py +++ b/allianceauth/services/modules/teamspeak3/util/ts3.py @@ -36,7 +36,7 @@ class TS3Proto: def connect(self, ip, port): try: - self._conn = telnetlib.Telnet(host=ip, port=port) + self._conn = telnetlib.Telnet(host=ip, port=port, timeout=5) self._connected = True except: # raise ConnectionError(ip, port) From 19282cac600fb78f6902920d1c1df47f2c333339 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 14 Apr 2018 13:53:41 -0400 Subject: [PATCH 19/47] Log messages from esi package. --- allianceauth/project_template/project_name/settings/base.py | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/allianceauth/project_template/project_name/settings/base.py b/allianceauth/project_template/project_name/settings/base.py index 45889964..49d00c35 100644 --- a/allianceauth/project_template/project_name/settings/base.py +++ b/allianceauth/project_template/project_name/settings/base.py @@ -166,7 +166,6 @@ CACHES = { } } -SECRET_KEY = 'this is a very bad secret key you should change' DEBUG = True ALLOWED_HOSTS = ['*'] DATABASES = { @@ -235,5 +234,9 @@ LOGGING = { 'handlers': ['log_file', 'console'], 'level': 'ERROR', }, + 'esi': { + 'handlers': ['log_file', 'console'], + 'level': 'DEBUG', + }, } } From df3acccc50cd882a4d1baed5505e9f385e162a44 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 14 Apr 2018 14:12:39 -0400 Subject: [PATCH 20/47] Correct matching start of URL patterns. Thanks @Peggle2k --- allianceauth/hrapplications/urls.py | 14 +++++++------- allianceauth/services/modules/teamspeak3/urls.py | 4 ++-- allianceauth/srp/urls.py | 4 ++-- allianceauth/urls.py | 2 +- 4 files changed, 12 insertions(+), 12 deletions(-) diff --git a/allianceauth/hrapplications/urls.py b/allianceauth/hrapplications/urls.py index 295ed1e6..efbcfe4a 100644 --- a/allianceauth/hrapplications/urls.py +++ b/allianceauth/hrapplications/urls.py @@ -13,19 +13,19 @@ urlpatterns = [ name="create_view"), url(r'^remove/(\w+)', views.hr_application_remove, name="remove"), - url(r'view/(\w+)', views.hr_application_view, + url(r'^view/(\w+)', views.hr_application_view, name="view"), - url(r'personal/view/(\w+)', views.hr_application_personal_view, + url(r'^personal/view/(\w+)', views.hr_application_personal_view, name="personal_view"), - url(r'personal/removal/(\w+)', + url(r'^personal/removal/(\w+)', views.hr_application_personal_removal, name="personal_removal"), - url(r'approve/(\w+)', views.hr_application_approve, + url(r'^approve/(\w+)', views.hr_application_approve, name="approve"), - url(r'reject/(\w+)', views.hr_application_reject, + url(r'^reject/(\w+)', views.hr_application_reject, name="reject"), - url(r'search/', views.hr_application_search, + url(r'^search/', views.hr_application_search, name="search"), - url(r'mark_in_progress/(\w+)', views.hr_application_mark_in_progress, + url(r'^mark_in_progress/(\w+)', views.hr_application_mark_in_progress, name="mark_in_progress"), ] diff --git a/allianceauth/services/modules/teamspeak3/urls.py b/allianceauth/services/modules/teamspeak3/urls.py index 98bf6992..4cc91c6c 100644 --- a/allianceauth/services/modules/teamspeak3/urls.py +++ b/allianceauth/services/modules/teamspeak3/urls.py @@ -10,11 +10,11 @@ module_urls = [ name='activate'), url(r'^deactivate/$', views.deactivate_teamspeak3, name='deactivate'), - url(r'reset_perm/$', views.reset_teamspeak3_perm, + url(r'^reset_perm/$', views.reset_teamspeak3_perm, name='reset_perm'), # Teamspeak Urls - url(r'verify/$', views.verify_teamspeak3, name='verify'), + url(r'^verify/$', views.verify_teamspeak3, name='verify'), ] urlpatterns = [ diff --git a/allianceauth/srp/urls.py b/allianceauth/srp/urls.py index 4a363a91..d357a7ce 100644 --- a/allianceauth/srp/urls.py +++ b/allianceauth/srp/urls.py @@ -23,9 +23,9 @@ urlpatterns = [ name='mark_uncompleted'), url(r'^request/remove/', views.srp_request_remove, name="request_remove"), - url(r'request/approve/', views.srp_request_approve, + url(r'^request/approve/', views.srp_request_approve, name='request_approve'), - url(r'request/reject/', views.srp_request_reject, + url(r'^request/reject/', views.srp_request_reject, name='request_reject'), url(r'^request/(\w+)/update', views.srp_request_update_amount, name="request_update_amount"), diff --git a/allianceauth/urls.py b/allianceauth/urls.py index 9eac2116..6edc43ac 100755 --- a/allianceauth/urls.py +++ b/allianceauth/urls.py @@ -26,7 +26,7 @@ urlpatterns = [ # Authentication url(r'', include(allianceauth.authentication.urls)), url(r'^account/login/$', TemplateView.as_view(template_name='public/login.html'), name='auth_login_user'), - url(r'account/', include(hmac_urls)), + url(r'^account/', include(hmac_urls)), # Admin urls url(r'^admin/', admin.site.urls), From 319cba86539d42d2a457df69c4f629a0491f4978 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 14 Apr 2018 15:13:42 -0400 Subject: [PATCH 21/47] Allow reconnecting characters to old users. Addresses #1007 --- allianceauth/authentication/admin.py | 19 +++++++-- allianceauth/authentication/backends.py | 15 ++++++- .../migrations/0016_ownershiprecord.py | 40 +++++++++++++++++++ allianceauth/authentication/models.py | 13 ++++++ allianceauth/authentication/signals.py | 14 ++++++- allianceauth/authentication/tests.py | 11 ++++- 6 files changed, 104 insertions(+), 8 deletions(-) create mode 100644 allianceauth/authentication/migrations/0016_ownershiprecord.py diff --git a/allianceauth/authentication/admin.py b/allianceauth/authentication/admin.py index 61183766..894ab2f5 100644 --- a/allianceauth/authentication/admin.py +++ b/allianceauth/authentication/admin.py @@ -6,7 +6,7 @@ from django.db.models import Q from allianceauth.services.hooks import ServicesHook from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed from django.dispatch import receiver -from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile +from allianceauth.authentication.models import State, get_guest_state, CharacterOwnership, UserProfile, OwnershipRecord from allianceauth.hooks import get_hooks from allianceauth.eveonline.models import EveCharacter from django.forms import ModelForm @@ -160,12 +160,23 @@ class StateAdmin(admin.ModelAdmin): return obj.userprofile_set.all().count() -@admin.register(CharacterOwnership) -class CharacterOwnershipAdmin(admin.ModelAdmin): +class BaseOwnershipAdmin(admin.ModelAdmin): list_display = ('user', 'character') search_fields = ('user__username', 'character__character_name', 'character__corporation_name', 'character__alliance_name') - readonly_fields = ('owner_hash', 'character') + def get_readonly_fields(self, request, obj=None): + if obj and obj.pk: + return 'owner_hash', 'character' + return tuple() + + +@admin.register(OwnershipRecord) +class OwnershipRecordAdmin(BaseOwnershipAdmin): + list_display = BaseOwnershipAdmin.list_display + ('created',) + + +@admin.register(CharacterOwnership) +class CharacterOwnershipAdmin(BaseOwnershipAdmin): def has_add_permission(self, request): return False diff --git a/allianceauth/authentication/backends.py b/allianceauth/authentication/backends.py index 281c7984..a37e4afd 100644 --- a/allianceauth/authentication/backends.py +++ b/allianceauth/authentication/backends.py @@ -2,7 +2,7 @@ from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import Permission from django.contrib.auth.models import User -from .models import UserProfile, CharacterOwnership +from .models import UserProfile, CharacterOwnership, OwnershipRecord class StateBackend(ModelBackend): @@ -43,7 +43,18 @@ class StateBackend(ModelBackend): CharacterOwnership.objects.create_by_token(token) return profile.user except UserProfile.DoesNotExist: - pass + # now we check historical records to see if this is a returning user + records = OwnershipRecord.objects.filter(owner_hash=token.character_owner_hash).filter(character__character_id=token.character_id) + if records.exists(): + # we've seen this character owner before. Re-attach to their old user account + user = records[0].user + token.user = user + co = CharacterOwnership.objects.create_by_token(token) + if not user.profile.main_character: + # set this as their main by default if they have none + user.profile.main_character = co.character + user.profile.save() + return user return self.create_user(token) def create_user(self, token): diff --git a/allianceauth/authentication/migrations/0016_ownershiprecord.py b/allianceauth/authentication/migrations/0016_ownershiprecord.py new file mode 100644 index 00000000..b2df6d8d --- /dev/null +++ b/allianceauth/authentication/migrations/0016_ownershiprecord.py @@ -0,0 +1,40 @@ +# Generated by Django 2.0.4 on 2018-04-14 18:28 + +from django.conf import settings +from django.db import migrations, models +import django.db.models.deletion + + +def create_initial_records(apps, schema_editor): + OwnershipRecord = apps.get_model('authentication', 'OwnershipRecord') + CharacterOwnership = apps.get_model('authentication', 'CharacterOwnership') + + OwnershipRecord.objects.bulk_create([ + OwnershipRecord(user=o.user, character=o.character, owner_hash=o.owner_hash) for o in CharacterOwnership.objects.all() + ]) + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('eveonline', '0009_on_delete'), + ('authentication', '0015_user_profiles'), + ] + + operations = [ + migrations.CreateModel( + name='OwnershipRecord', + fields=[ + ('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('owner_hash', models.CharField(db_index=True, max_length=28)), + ('created', models.DateTimeField(auto_now=True)), + ('character', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to='eveonline.EveCharacter')), + ('user', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='ownership_records', to=settings.AUTH_USER_MODEL)), + ], + options={ + 'ordering': ['-created'], + }, + ), + migrations.RunPython(create_initial_records, migrations.RunPython.noop) + ] diff --git a/allianceauth/authentication/models.py b/allianceauth/authentication/models.py index 84b5dc58..aa565219 100755 --- a/allianceauth/authentication/models.py +++ b/allianceauth/authentication/models.py @@ -96,3 +96,16 @@ class CharacterOwnership(models.Model): def __str__(self): return "%s: %s" % (self.user, self.character) + + +class OwnershipRecord(models.Model): + character = models.ForeignKey(EveCharacter, on_delete=models.CASCADE, related_name='ownership_records') + owner_hash = models.CharField(max_length=28, db_index=True) + user = models.ForeignKey(User, on_delete=models.CASCADE, related_name='ownership_records') + created = models.DateTimeField(auto_now=True) + + class Meta: + ordering = ['-created'] + + def __str__(self): + return "%s: %s on %s" % (self.user, self.character, self.created) \ No newline at end of file diff --git a/allianceauth/authentication/signals.py b/allianceauth/authentication/signals.py index e774a2fc..d8e73fdf 100644 --- a/allianceauth/authentication/signals.py +++ b/allianceauth/authentication/signals.py @@ -1,6 +1,6 @@ import logging -from .models import CharacterOwnership, UserProfile, get_guest_state, State +from .models import CharacterOwnership, UserProfile, get_guest_state, State, OwnershipRecord from django.contrib.auth.models import User from django.db.models import Q from django.db.models.signals import pre_save, post_save, pre_delete, post_delete, m2m_changed @@ -153,3 +153,15 @@ def check_state_on_character_update(sender, instance, *args, **kwargs): except UserProfile.DoesNotExist: logger.debug("Character {0} is not a main character. No state assessment required.".format(instance)) pass + + +@receiver(post_save, sender=CharacterOwnership) +def ownership_record_creation(sender, instance, created, *args, **kwargs): + if created: + records = OwnershipRecord.objects.filter(owner_hash=instance.owner_hash).filter(character=instance.character) + if records.exists(): + if records[0].user == instance.user: # most recent record is sorted first + logger.debug("Already have ownership record of {0} by user {1}".format(instance.character, instance.user)) + return + logger.info("Character {0} has a new owner {1}. Creating ownership record.".format(instance.character, instance.user)) + OwnershipRecord.objects.create(user=instance.user, character=instance.character, owner_hash=instance.owner_hash) \ No newline at end of file diff --git a/allianceauth/authentication/tests.py b/allianceauth/authentication/tests.py index ae1d119c..fb63f52e 100644 --- a/allianceauth/authentication/tests.py +++ b/allianceauth/authentication/tests.py @@ -3,7 +3,7 @@ from unittest import mock from django.test import TestCase from django.contrib.auth.models import User from allianceauth.tests.auth_utils import AuthUtils -from .models import CharacterOwnership, UserProfile, State, get_guest_state +from .models import CharacterOwnership, UserProfile, State, get_guest_state, OwnershipRecord from .backends import StateBackend from .tasks import check_character_ownership from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo @@ -90,6 +90,7 @@ class BackendTestCase(TestCase): corporation_ticker='CORP', ) cls.user = AuthUtils.create_user('test_user', disconnect_signals=True) + cls.old_user = AuthUtils.create_user('old_user', disconnect_signals=True) AuthUtils.disconnect_signals() CharacterOwnership.objects.create(user=cls.user, character=cls.main_character, owner_hash='1') CharacterOwnership.objects.create(user=cls.user, character=cls.alt_character, owner_hash='2') @@ -113,6 +114,14 @@ class BackendTestCase(TestCase): self.assertEqual(user.username, 'Unclaimed_Character') self.assertEqual(user.profile.main_character, self.unclaimed_character) + def test_authenticate_character_record(self): + t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='4') + record = OwnershipRecord.objects.create(user=self.old_user, character=self.unclaimed_character, owner_hash='4') + user = StateBackend().authenticate(t) + self.assertEqual(user, self.old_user) + self.assertTrue(CharacterOwnership.objects.filter(owner_hash='4', user=self.old_user).exists()) + self.assertTrue(user.profile.main_character) + def test_iterate_username(self): t = Token(character_id=self.unclaimed_character.character_id, character_name=self.unclaimed_character.character_name, character_owner_hash='3') From e52478c9aa2103fc6d309582e72a944cec92eb2d Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 16 Apr 2018 19:36:41 -0400 Subject: [PATCH 22/47] Correct URL template tag. Thanks @Peggle2K --- .../hrapplications/templates/hrapplications/management.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/allianceauth/hrapplications/templates/hrapplications/management.html b/allianceauth/hrapplications/templates/hrapplications/management.html index 59994f58..29e4bdcc 100644 --- a/allianceauth/hrapplications/templates/hrapplications/management.html +++ b/allianceauth/hrapplications/templates/hrapplications/management.html @@ -155,7 +155,7 @@ {% if perms.hrapplications.delete_application %} - From 21782293eadfba7c12e6535632be2e1636560d68 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 17 Apr 2018 12:08:39 -0400 Subject: [PATCH 23/47] Create missing Corp/Alliance models. Thanks @Lof79 --- allianceauth/eveonline/autogroups/models.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/allianceauth/eveonline/autogroups/models.py b/allianceauth/eveonline/autogroups/models.py index 8f5bbe9e..9c9196c4 100644 --- a/allianceauth/eveonline/autogroups/models.py +++ b/allianceauth/eveonline/autogroups/models.py @@ -124,8 +124,9 @@ class AutogroupsConfig(models.Model): return group = self.get_alliance_group(alliance) except EveAllianceInfo.DoesNotExist: - logger.warning('User {} main characters alliance does not exist in the database.' - ' Group membership not updated'.format(user)) + logger.debug('User {} main characters alliance does not exist in the database. Creating.'.format(user)) + alliance = EveAllianceInfo.objects.create_alliance(user.profile.main_character.alliance_id) + group = self.get_alliance_group(alliance) except AttributeError: logger.warning('User {} does not have a main character. Group membership not updated'.format(user)) finally: @@ -144,8 +145,9 @@ class AutogroupsConfig(models.Model): corp = user.profile.main_character.corporation group = self.get_corp_group(corp) except EveCorporationInfo.DoesNotExist: - logger.warning('User {} main characters corporation does not exist in the database.' - ' Group membership not updated'.format(user)) + logger.debug('User {} main characters corporation does not exist in the database. Creating.'.format(user)) + corp = EveCorporationInfo.objects.create_corporation(user.profile.main_character.corporation_id) + group = self.get_corp_group(corp) except AttributeError: logger.warning('User {} does not have a main character. Group membership not updated'.format(user)) finally: From 6c7b65edad4e61b518e1d18589a231fcfa01bad4 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 17 Apr 2018 16:08:13 -0400 Subject: [PATCH 24/47] Record alliance ticker in character model. Closes #1018 --- allianceauth/eveonline/managers.py | 1 + .../migrations/0010_alliance_ticker.py | 23 +++++++++++++++++++ allianceauth/eveonline/models.py | 4 +++- 3 files changed, 27 insertions(+), 1 deletion(-) create mode 100644 allianceauth/eveonline/migrations/0010_alliance_ticker.py diff --git a/allianceauth/eveonline/managers.py b/allianceauth/eveonline/managers.py index 42ae9bd2..1276736c 100644 --- a/allianceauth/eveonline/managers.py +++ b/allianceauth/eveonline/managers.py @@ -26,6 +26,7 @@ class EveCharacterManager(models.Manager): corporation_ticker=character.corp.ticker, alliance_id=character.alliance.id, alliance_name=character.alliance.name, + alliance_ticker=getattr(character.alliance, 'ticker', None), ) def update_character(self, character_id): diff --git a/allianceauth/eveonline/migrations/0010_alliance_ticker.py b/allianceauth/eveonline/migrations/0010_alliance_ticker.py new file mode 100644 index 00000000..f537cc25 --- /dev/null +++ b/allianceauth/eveonline/migrations/0010_alliance_ticker.py @@ -0,0 +1,23 @@ +# Generated by Django 2.0.4 on 2018-04-17 20:07 + +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + ('eveonline', '0009_on_delete'), + ] + + operations = [ + migrations.AddField( + model_name='evecharacter', + name='alliance_ticker', + field=models.CharField(blank=True, default='', max_length=5, null=True), + ), + migrations.AlterField( + model_name='evecharacter', + name='corporation_ticker', + field=models.CharField(max_length=5), + ), + ] diff --git a/allianceauth/eveonline/models.py b/allianceauth/eveonline/models.py index 5e602e94..2edbc5c4 100644 --- a/allianceauth/eveonline/models.py +++ b/allianceauth/eveonline/models.py @@ -84,9 +84,10 @@ class EveCharacter(models.Model): character_name = models.CharField(max_length=254, unique=True) corporation_id = models.CharField(max_length=254) corporation_name = models.CharField(max_length=254) - corporation_ticker = models.CharField(max_length=254) + corporation_ticker = models.CharField(max_length=5) alliance_id = models.CharField(max_length=254, blank=True, null=True, default='') alliance_name = models.CharField(max_length=254, blank=True, null=True, default='') + alliance_ticker = models.CharField(max_length=5, blank=True, null=True, default='') objects = EveCharacterManager() provider = EveCharacterProviderManager() @@ -120,6 +121,7 @@ class EveCharacter(models.Model): self.corporation_ticker = character.corp.ticker self.alliance_id = character.alliance.id self.alliance_name = character.alliance.name + self.alliance_ticker = getattr(character.alliance, 'ticker', None) self.save() return self From 20236cab8a9fd1fc575b078b280e0b83b7599eae Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 17 Apr 2018 16:09:50 -0400 Subject: [PATCH 25/47] Use alliance ticker stored in character table. --- allianceauth/services/hooks.py | 10 +--------- allianceauth/services/tests/test_nameformatter.py | 7 ++++++- 2 files changed, 7 insertions(+), 10 deletions(-) diff --git a/allianceauth/services/hooks.py b/allianceauth/services/hooks.py index 3e564bbe..44c90900 100644 --- a/allianceauth/services/hooks.py +++ b/allianceauth/services/hooks.py @@ -1,10 +1,8 @@ from django.conf.urls import include, url from django.template.loader import render_to_string from django.utils.functional import cached_property -from django.core.exceptions import ObjectDoesNotExist from django.conf import settings from string import Formatter -import re from allianceauth.hooks import get_hooks @@ -161,16 +159,10 @@ class NameFormatter: 'corp_id': getattr(main_char, 'corporation_id', None), 'alliance_name': getattr(main_char, 'alliance_name', None), 'alliance_id': getattr(main_char, 'alliance_id', None), - 'alliance_ticker': None, + 'alliance_ticker': getattr(main_char, 'alliance_ticker', None), 'username': self.user.username, } - if main_char is not None and re.match('.*alliance(?:_or_corp)?_ticker', self.string_formatter): - # Reduces db lookups - try: - format_data['alliance_ticker'] = getattr(getattr(main_char, 'alliance', None), 'alliance_ticker', None) - except ObjectDoesNotExist: - pass format_data['alliance_or_corp_name'] = format_data['alliance_name'] or format_data['corp_name'] format_data['alliance_or_corp_ticker'] = format_data['alliance_ticker'] or format_data['corp_ticker'] return format_data diff --git a/allianceauth/services/tests/test_nameformatter.py b/allianceauth/services/tests/test_nameformatter.py index 79f328c1..2b448bc7 100644 --- a/allianceauth/services/tests/test_nameformatter.py +++ b/allianceauth/services/tests/test_nameformatter.py @@ -33,6 +33,7 @@ class NameFormatterTestCase(TestCase): corporation_ticker='TIKK', alliance_id='3456', alliance_name='alliance name', + alliance_ticker='TIKR', ) self.member.profile.main_character = self.char self.member.profile.save() @@ -83,11 +84,15 @@ class NameFormatterTestCase(TestCase): self.assertIn('alliance_name', result) self.assertEqual(result['alliance_name'], self.char.alliance_name) self.assertIn('alliance_ticker', result) - self.assertEqual(result['alliance_ticker'], self.char.alliance.alliance_ticker) + self.assertEqual(result['alliance_ticker'], self.char.alliance_ticker) self.assertIn('alliance_id', result) self.assertEqual(result['alliance_id'], self.char.alliance_id) self.assertIn('username', result) self.assertEqual(result['username'], self.member.username) + self.assertIn('alliance_or_corp_name', result) + self.assertEqual(result['alliance_or_corp_name'], self.char.alliance_name) + self.assertIn('alliance_or_corp_ticker', result) + self.assertEqual(result['alliance_or_corp_ticker'], self.char.alliance_ticker) def test_format_name(self): config = NameFormatConfig.objects.create( From 73e6f576f453c231fac82ac92918d4621a08bbff Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 19 Mar 2018 20:39:27 -0400 Subject: [PATCH 26/47] Use celery_once to prevent repeat task queueing. Prevent group updates from being queued multiple times per user. Default graceful to prevent raising exceptions. --- allianceauth/project_template/project_name/celery.py | 7 +++++++ allianceauth/services/modules/discord/tasks.py | 5 +++-- allianceauth/services/modules/discourse/tasks.py | 3 ++- allianceauth/services/modules/mumble/tasks.py | 4 ++-- allianceauth/services/modules/openfire/tasks.py | 4 ++-- allianceauth/services/modules/phpbb3/tasks.py | 4 ++-- allianceauth/services/modules/seat/tasks.py | 4 ++-- allianceauth/services/modules/smf/tasks.py | 4 ++-- allianceauth/services/modules/teamspeak3/tasks.py | 4 ++-- allianceauth/services/tasks.py | 6 ++++++ setup.py | 1 + tests/celery.py | 7 +++++++ 12 files changed, 38 insertions(+), 15 deletions(-) diff --git a/allianceauth/project_template/project_name/celery.py b/allianceauth/project_template/project_name/celery.py index dc9b4d39..aaed7fb4 100644 --- a/allianceauth/project_template/project_name/celery.py +++ b/allianceauth/project_template/project_name/celery.py @@ -11,6 +11,13 @@ app = Celery('{{ project_name }}') # Using a string here means the worker don't have to serialize # the configuration object to child processes. app.config_from_object('django.conf:settings') +app.conf.ONCE = { + 'backend': 'celery_once.backends.Redis', + 'settings': { + 'url': 'redis://localhost:6379/0', + 'default_timeout': 60 * 60 + } +} # Load task modules from all registered Django app configs. app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) diff --git a/allianceauth/services/modules/discord/tasks.py b/allianceauth/services/modules/discord/tasks.py index 86bb06f2..8995e267 100644 --- a/allianceauth/services/modules/discord/tasks.py +++ b/allianceauth/services/modules/discord/tasks.py @@ -9,6 +9,7 @@ from requests.exceptions import HTTPError from allianceauth.services.hooks import NameFormatter from .manager import DiscordOAuthManager, DiscordApiBackoff from .models import DiscordUser +from allianceauth.services.tasks import QueueOnce logger = logging.getLogger(__name__) @@ -58,7 +59,7 @@ class DiscordTasks: return True @staticmethod - @shared_task(bind=True, name='discord.update_groups') + @shared_task(bind=True, name='discord.update_groups', base=QueueOnce) def update_groups(task_self, pk): user = User.objects.get(pk=pk) logger.debug("Updating discord groups for user %s" % user) @@ -99,7 +100,7 @@ class DiscordTasks: DiscordTasks.update_groups.delay(discord_user.user.pk) @staticmethod - @shared_task(bind=True, name='discord.update_nickname') + @shared_task(bind=True, name='discord.update_nickname', base=QueueOnce) def update_nickname(task_self, pk): user = User.objects.get(pk=pk) logger.debug("Updating discord nickname for user %s" % user) diff --git a/allianceauth/services/modules/discourse/tasks.py b/allianceauth/services/modules/discourse/tasks.py index 43cd4dd6..262d1525 100644 --- a/allianceauth/services/modules/discourse/tasks.py +++ b/allianceauth/services/modules/discourse/tasks.py @@ -6,6 +6,7 @@ from celery import shared_task from allianceauth.notifications import notify from allianceauth.services.hooks import NameFormatter +from allianceauth.services.tasks import QueueOnce from .manager import DiscourseManager from .models import DiscourseUser @@ -40,7 +41,7 @@ class DiscourseTasks: return False @staticmethod - @shared_task(bind=True, name='discourse.update_groups') + @shared_task(bind=True, name='discourse.update_groups', base=QueueOnce) def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating discourse groups for user %s" % user) diff --git a/allianceauth/services/modules/mumble/tasks.py b/allianceauth/services/modules/mumble/tasks.py index 541a0990..02450e93 100644 --- a/allianceauth/services/modules/mumble/tasks.py +++ b/allianceauth/services/modules/mumble/tasks.py @@ -3,7 +3,7 @@ import logging from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from celery import shared_task - +from allianceauth.services.tasks import QueueOnce from .models import MumbleUser logger = logging.getLogger(__name__) @@ -26,7 +26,7 @@ class MumbleTasks: MumbleUser.objects.all().delete() @staticmethod - @shared_task(bind=True, name="mumble.update_groups") + @shared_task(bind=True, name="mumble.update_groups", base=QueueOnce) def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating mumble groups for user %s" % user) diff --git a/allianceauth/services/modules/openfire/tasks.py b/allianceauth/services/modules/openfire/tasks.py index 4d6bcf8d..3993b6b6 100644 --- a/allianceauth/services/modules/openfire/tasks.py +++ b/allianceauth/services/modules/openfire/tasks.py @@ -4,7 +4,7 @@ from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from allianceauth.notifications import notify from celery import shared_task - +from allianceauth.services.tasks import QueueOnce from allianceauth.services.modules.openfire.manager import OpenfireManager from allianceauth.services.hooks import NameFormatter from .models import OpenfireUser @@ -40,7 +40,7 @@ class OpenfireTasks: OpenfireUser.objects.all().delete() @staticmethod - @shared_task(bind=True, name="openfire.update_groups") + @shared_task(bind=True, name="openfire.update_groups", base=QueueOnce) def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating jabber groups for user %s" % user) diff --git a/allianceauth/services/modules/phpbb3/tasks.py b/allianceauth/services/modules/phpbb3/tasks.py index 107ead86..6c9b7503 100644 --- a/allianceauth/services/modules/phpbb3/tasks.py +++ b/allianceauth/services/modules/phpbb3/tasks.py @@ -3,7 +3,7 @@ import logging from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from celery import shared_task - +from allianceauth.services.tasks import QueueOnce from allianceauth.notifications import notify from allianceauth.services.hooks import NameFormatter from .manager import Phpbb3Manager @@ -35,7 +35,7 @@ class Phpbb3Tasks: return False @staticmethod - @shared_task(bind=True, name="phpbb3.update_groups") + @shared_task(bind=True, name="phpbb3.update_groups", base=QueueOnce) def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating phpbb3 groups for user %s" % user) diff --git a/allianceauth/services/modules/seat/tasks.py b/allianceauth/services/modules/seat/tasks.py index 0ed4e2a1..e0f61186 100644 --- a/allianceauth/services/modules/seat/tasks.py +++ b/allianceauth/services/modules/seat/tasks.py @@ -3,7 +3,7 @@ import logging from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from celery import shared_task - +from allianceauth.services.tasks import QueueOnce from allianceauth.notifications import notify from allianceauth.services.hooks import NameFormatter from .manager import SeatManager @@ -34,7 +34,7 @@ class SeatTasks: return False @staticmethod - @shared_task(bind=True) + @shared_task(bind=True, name='seat.update_roles', base=QueueOnce) def update_roles(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating SeAT roles for user %s" % user) diff --git a/allianceauth/services/modules/smf/tasks.py b/allianceauth/services/modules/smf/tasks.py index 91b45732..847fb52c 100644 --- a/allianceauth/services/modules/smf/tasks.py +++ b/allianceauth/services/modules/smf/tasks.py @@ -3,7 +3,7 @@ import logging from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from celery import shared_task - +from allianceauth.services.tasks import QueueOnce from allianceauth.notifications import notify from allianceauth.services.hooks import NameFormatter from .manager import SmfManager @@ -39,7 +39,7 @@ class SmfTasks: SmfUser.objects.all().delete() @staticmethod - @shared_task(bind=True, name="smf.update_groups") + @shared_task(bind=True, name="smf.update_groups", base=QueueOnce) def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating smf groups for user %s" % user) diff --git a/allianceauth/services/modules/teamspeak3/tasks.py b/allianceauth/services/modules/teamspeak3/tasks.py index 0d2fa98d..96779e3b 100644 --- a/allianceauth/services/modules/teamspeak3/tasks.py +++ b/allianceauth/services/modules/teamspeak3/tasks.py @@ -3,7 +3,7 @@ import logging from django.contrib.auth.models import User from django.core.exceptions import ObjectDoesNotExist from celery import shared_task - +from allianceauth.services.tasks import QueueOnce from allianceauth.notifications import notify from allianceauth.services.hooks import NameFormatter from .manager import Teamspeak3Manager @@ -56,7 +56,7 @@ class Teamspeak3Tasks: logger.info("Teamspeak3 disabled") @staticmethod - @shared_task(bind=True, name="teamspeak3.update_groups") + @shared_task(bind=True, name="teamspeak3.update_groups", base=QueueOnce) def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating user %s teamspeak3 groups" % user) diff --git a/allianceauth/services/tasks.py b/allianceauth/services/tasks.py index d3bcdd16..545aa7a9 100644 --- a/allianceauth/services/tasks.py +++ b/allianceauth/services/tasks.py @@ -4,12 +4,18 @@ import redis from celery import shared_task from django.contrib.auth.models import User from .hooks import ServicesHook +from celery_once import QueueOnce as BaseTask REDIS_CLIENT = redis.Redis() logger = logging.getLogger(__name__) +class QueueOnce(BaseTask): + once = BaseTask.once + once['graceful'] = True + + # http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html def only_one(function=None, key="", timeout=None): """Enforce only one celery task at a time.""" diff --git a/setup.py b/setup.py index 882a439f..049a34ca 100644 --- a/setup.py +++ b/setup.py @@ -14,6 +14,7 @@ install_requires = [ 'redis', 'celery>=4.0.2', + 'celery_once', 'django>=1.11', 'django-bootstrap-form', diff --git a/tests/celery.py b/tests/celery.py index 6492058a..220dbb35 100644 --- a/tests/celery.py +++ b/tests/celery.py @@ -7,6 +7,13 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') from django.conf import settings # noqa app = Celery('devauth') +app.conf.ONCE = { + 'backend': 'celery_once.backends.Redis', + 'settings': { + 'url': 'redis://localhost:6379/0', + 'default_timeout': 60 * 60 + } +} # Using a string here means the worker don't have to serialize # the configuration object to child processes. From f3f156bf570a0e73b1223a30ba215a0c17406ce1 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Fri, 23 Mar 2018 14:43:15 -0400 Subject: [PATCH 27/47] Use Django's cache framework for task keys. Remove depreciated only_one decorator. Prevent including task_self repr in key name. Because some tasks are nested in a class, they use a task_self argument instead of the normal self which the celery_once package doesn't recognize to strip out. --- .../project_template/project_name/celery.py | 7 +-- allianceauth/services/tasks.py | 60 +++++++++++-------- tests/celery.py | 14 ++--- 3 files changed, 43 insertions(+), 38 deletions(-) diff --git a/allianceauth/project_template/project_name/celery.py b/allianceauth/project_template/project_name/celery.py index aaed7fb4..247c5361 100644 --- a/allianceauth/project_template/project_name/celery.py +++ b/allianceauth/project_template/project_name/celery.py @@ -12,11 +12,8 @@ app = Celery('{{ project_name }}') # the configuration object to child processes. app.config_from_object('django.conf:settings') app.conf.ONCE = { - 'backend': 'celery_once.backends.Redis', - 'settings': { - 'url': 'redis://localhost:6379/0', - 'default_timeout': 60 * 60 - } + 'backend': 'allianceauth.services.tasks.DjangoBackend', + 'settings': {} } # Load task modules from all registered Django app configs. diff --git a/allianceauth/services/tasks.py b/allianceauth/services/tasks.py index 545aa7a9..5e812adf 100644 --- a/allianceauth/services/tasks.py +++ b/allianceauth/services/tasks.py @@ -1,12 +1,13 @@ import logging -import redis -from celery import shared_task +from celery import shared_task, Task from django.contrib.auth.models import User from .hooks import ServicesHook -from celery_once import QueueOnce as BaseTask +from celery_once import QueueOnce as BaseTask, AlreadyQueued +from celery_once.helpers import now_unix, queue_once_key +from django.core.cache import cache +from inspect import getcallargs -REDIS_CLIENT = redis.Redis() logger = logging.getLogger(__name__) @@ -15,32 +16,41 @@ class QueueOnce(BaseTask): once = BaseTask.once once['graceful'] = True + def get_key(self, args=None, kwargs=None): + """ + Generate the key from the name of the task (e.g. 'tasks.example') and + args/kwargs. + """ + restrict_to = self.once.get('keys', None) + args = args or {} + kwargs = kwargs or {} + call_args = getcallargs(self.run, *args, **kwargs) -# http://loose-bits.com/2010/10/distributed-task-locking-in-celery.html -def only_one(function=None, key="", timeout=None): - """Enforce only one celery task at a time.""" + if isinstance(call_args.get('self'), Task): + del call_args['self'] + if isinstance(call_args.get('task_self'), Task): + del call_args['task_self'] + return queue_once_key(self.name, call_args, restrict_to) - def _dec(run_func): - """Decorator.""" - def _caller(*args, **kwargs): - """Caller.""" - ret_value = None - have_lock = False - lock = REDIS_CLIENT.lock(key, timeout=timeout) - try: - have_lock = lock.acquire(blocking=False) - if have_lock: - ret_value = run_func(*args, **kwargs) - finally: - if have_lock: - lock.release() +class DjangoBackend: + def __init__(self, settings): + pass - return ret_value + @staticmethod + def raise_or_lock(key, timeout): + now = now_unix() + result = cache.get(key) + if result: + remaining = int(result) - now + if remaining > 0: + raise AlreadyQueued(remaining) + else: + cache.set(key, now + timeout, timeout) - return _caller - - return _dec(function) if function is not None else _dec + @staticmethod + def clear_lock(key): + return cache.delete(key) @shared_task(bind=True) diff --git a/tests/celery.py b/tests/celery.py index 220dbb35..ee162f68 100644 --- a/tests/celery.py +++ b/tests/celery.py @@ -1,4 +1,4 @@ -import os, sys +import os from celery import Celery # set the default Django settings module for the 'celery' program. @@ -7,17 +7,15 @@ os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'settings') from django.conf import settings # noqa app = Celery('devauth') -app.conf.ONCE = { - 'backend': 'celery_once.backends.Redis', - 'settings': { - 'url': 'redis://localhost:6379/0', - 'default_timeout': 60 * 60 - } -} # Using a string here means the worker don't have to serialize # the configuration object to child processes. app.config_from_object('django.conf:settings') +app.conf.ONCE = { + 'backend': 'allianceauth.services.tasks.DjangoBackend', + 'settings': {} +} # Load task modules from all registered Django app configs. app.autodiscover_tasks(lambda: settings.INSTALLED_APPS) + From 08f89d28445c4b980ac99c48e0af9dcde8b1b751 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 7 Apr 2018 21:06:11 -0400 Subject: [PATCH 28/47] Stop using task_self in bound tasks. --- .../services/modules/discord/tasks.py | 16 +++++++------- allianceauth/services/tasks.py | 21 ++----------------- 2 files changed, 10 insertions(+), 27 deletions(-) diff --git a/allianceauth/services/modules/discord/tasks.py b/allianceauth/services/modules/discord/tasks.py index 8995e267..4edc96c5 100644 --- a/allianceauth/services/modules/discord/tasks.py +++ b/allianceauth/services/modules/discord/tasks.py @@ -60,7 +60,7 @@ class DiscordTasks: @staticmethod @shared_task(bind=True, name='discord.update_groups', base=QueueOnce) - def update_groups(task_self, pk): + def update_groups(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating discord groups for user %s" % user) if DiscordTasks.has_account(user): @@ -71,7 +71,7 @@ class DiscordTasks: except DiscordApiBackoff as bo: logger.info("Discord group sync API back off for %s, " "retrying in %s seconds" % (user, bo.retry_after_seconds)) - raise task_self.retry(countdown=bo.retry_after_seconds) + raise self.retry(countdown=bo.retry_after_seconds) except HTTPError as e: if e.response.status_code == 404: try: @@ -82,9 +82,9 @@ class DiscordTasks: finally: raise e except Exception as e: - if task_self: + if self: logger.exception("Discord group sync failed for %s, retrying in 10 mins" % user) - raise task_self.retry(countdown=60 * 10) + raise self.retry(countdown=60 * 10) else: # Rethrow raise e @@ -101,7 +101,7 @@ class DiscordTasks: @staticmethod @shared_task(bind=True, name='discord.update_nickname', base=QueueOnce) - def update_nickname(task_self, pk): + def update_nickname(self, pk): user = User.objects.get(pk=pk) logger.debug("Updating discord nickname for user %s" % user) if DiscordTasks.has_account(user): @@ -113,11 +113,11 @@ class DiscordTasks: except DiscordApiBackoff as bo: logger.info("Discord nickname update API back off for %s, " "retrying in %s seconds" % (user, bo.retry_after_seconds)) - raise task_self.retry(countdown=bo.retry_after_seconds) + raise self.retry(countdown=bo.retry_after_seconds) except Exception as e: - if task_self: + if self: logger.exception("Discord nickname sync failed for %s, retrying in 10 mins" % user) - raise task_self.retry(countdown=60 * 10) + raise self.retry(countdown=60 * 10) else: # Rethrow raise e diff --git a/allianceauth/services/tasks.py b/allianceauth/services/tasks.py index 5e812adf..9803a4d1 100644 --- a/allianceauth/services/tasks.py +++ b/allianceauth/services/tasks.py @@ -1,12 +1,11 @@ import logging -from celery import shared_task, Task +from celery import shared_task from django.contrib.auth.models import User from .hooks import ServicesHook from celery_once import QueueOnce as BaseTask, AlreadyQueued -from celery_once.helpers import now_unix, queue_once_key +from celery_once.helpers import now_unix from django.core.cache import cache -from inspect import getcallargs logger = logging.getLogger(__name__) @@ -16,22 +15,6 @@ class QueueOnce(BaseTask): once = BaseTask.once once['graceful'] = True - def get_key(self, args=None, kwargs=None): - """ - Generate the key from the name of the task (e.g. 'tasks.example') and - args/kwargs. - """ - restrict_to = self.once.get('keys', None) - args = args or {} - kwargs = kwargs or {} - call_args = getcallargs(self.run, *args, **kwargs) - - if isinstance(call_args.get('self'), Task): - del call_args['self'] - if isinstance(call_args.get('task_self'), Task): - del call_args['task_self'] - return queue_once_key(self.name, call_args, restrict_to) - class DjangoBackend: def __init__(self, settings): From cd8bcfbbb514626b4362f24d433d81779c582c63 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 17 Apr 2018 17:40:08 -0400 Subject: [PATCH 29/47] Build from github to fix tests in py37-dj20 py37-dj111 still fails for some reason. The only difference in the problematic method between 1.11.12 and 2.0 is whitespace. It's fine in py37-dj20 which is all we really care about so I'm inclined to ignore that issue. py37-dj111 isn't even tested on Travis CI so its failure won't be a problem. Both django-celery-beat and adarnauth-esi have put out releases supporting dj20 so it's not necessary to build from their source. --- tox.ini | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tox.ini b/tox.ini index 2fe1cc59..d0f314c9 100644 --- a/tox.ini +++ b/tox.ini @@ -14,7 +14,7 @@ basepython = deps= dj111: Django>=1.11.1,<2.0 dj20: Django>=2.0 - dj20: https://github.com/celery/django-celery-beat/zipball/master#egg=django-celery-beat - dj20: https://github.com/Adarnof/adarnauth-esi/zipball/master#egg=adarnauth-esi + py37: https://github.com/yaml/pyyaml/zipball/master#egg=pyyaml + py37: https://github.com/celery/kombu/zipball/master#egg=kombu install_command = pip install -e ".[testing]" -U {opts} {packages} commands=coverage run runtests.py -v 2 From 95f72c854d1dd064efd40bc6a17c0a671ef789c2 Mon Sep 17 00:00:00 2001 From: Ariel Rin Date: Wed, 18 Apr 2018 08:55:18 +1000 Subject: [PATCH 30/47] Minor Documentation Update (#1019) Gunicorn needs to be run whilist in the folder for context, folder path is not enough Correct static path, note to check nginx user Capitalization of services and small typos Service examples updated to their latest versions and download links Expanded /var/www chowns for Nginx and Apache examples Add in a troubleshooting note for no images being displayed (permissions issue) and gunicorn not execting (file path context) Correct formatting. Reword a few parts. Remove "new in 1.15". --- .../project_name/settings/local.py | 3 +- docs/conf.py | 6 +-- docs/development/documentation.md | 2 +- docs/development/integrating-services.md | 20 +++++----- docs/features/autogroups.md | 8 ++-- docs/features/corpstats.md | 26 ++++++------- docs/features/fleetup.md | 4 +- docs/features/hrapplications.md | 12 +++--- docs/features/nameformats.md | 10 ++--- docs/features/permissions_tool.md | 5 --- docs/features/states.md | 8 ++-- docs/installation/auth/allianceauth.md | 24 ++++++------ docs/installation/auth/apache.md | 4 +- docs/installation/auth/gunicorn.md | 4 +- docs/installation/auth/nginx.md | 17 ++++++--- docs/installation/services/discord.md | 20 +++++----- docs/installation/services/discourse.md | 14 +++---- docs/installation/services/market.md | 22 +++++------ docs/installation/services/mumble.md | 14 +++---- docs/installation/services/openfire.md | 30 +++++++++------ docs/installation/services/permissions.md | 8 +--- docs/installation/services/phpbb3.md | 37 ++++++++++-------- docs/installation/services/smf.md | 38 +++++++++++-------- docs/installation/services/teamspeak3.md | 22 +++++------ docs/installation/services/xenforo.md | 2 +- docs/maintenance/project.md | 14 +++---- docs/maintenance/troubleshooting.md | 10 ++++- 27 files changed, 203 insertions(+), 181 deletions(-) diff --git a/allianceauth/project_template/project_name/settings/local.py b/allianceauth/project_template/project_name/settings/local.py index 039feebd..3d3b3a2d 100644 --- a/allianceauth/project_template/project_name/settings/local.py +++ b/allianceauth/project_template/project_name/settings/local.py @@ -42,8 +42,9 @@ ESI_SSO_CLIENT_SECRET = '' ESI_SSO_CALLBACK_URL = '' # By default emails are validated before new users can log in. -# It's recommended to use a free service like SparkPost or Mailgun to send email. +# It's recommended to use a free service like SparkPost or Elastic Email to send email. # https://www.sparkpost.com/docs/integrations/django/ +# https://elasticemail.com/resources/settings/smtp-api/ # Set the default from email to something like 'noreply@example.com' # Email validation can be turned off by uncommenting the line below. This can break some services. # REGISTRATION_VERIFY_EMAIL = False diff --git a/docs/conf.py b/docs/conf.py index 5a1a38f0..5cc9806d 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -54,7 +54,7 @@ master_doc = 'index' # General information about the project. project = u'Alliance Auth' -copyright = u'2017, Alliance Auth' +copyright = u'2018, Alliance Auth' author = u'R4stl1n' # The version info for the project you're documenting, acts as replacement for @@ -62,7 +62,7 @@ author = u'R4stl1n' # built documents. # # The short X.Y version. -version = u'1.14' +version = u'2.0' # The full version, including alpha/beta/rc tags. # release = u'1.14.0' @@ -156,7 +156,7 @@ man_pages = [ # dir menu entry, description, category) texinfo_documents = [ (master_doc, 'AllianceAuth', u'Alliance Auth Documentation', - author, 'AllianceAuth', 'Alliance service auth to help large scale alliances manage services.', + author, 'AllianceAuth', 'An auth system for EVE Online to help in-game organizations manage online service access.', 'Miscellaneous'), ] diff --git a/docs/development/documentation.md b/docs/development/documentation.md index 404b32d3..a79b905e 100644 --- a/docs/development/documentation.md +++ b/docs/development/documentation.md @@ -5,7 +5,7 @@ The documentation for Alliance Auth uses [Sphinx](http://www.sphinx-doc.org/) to [readthedocs.org](https://readthedocs.org/). -Documentation was migrated from the Github wiki pages and into the repository to allow documentation changes to be +Documentation was migrated from the GitHub wiki pages and into the repository to allow documentation changes to be included with pull requests. This means that documentation can be guaranteed to be updated when a pull request is accepted rather than hoping documentation is updated afterwards or relying on maintainers to do the work. It also allows for documentation to be maintained at different versions more easily. diff --git a/docs/development/integrating-services.md b/docs/development/integrating-services.md index 718de2b7..c7c27c41 100644 --- a/docs/development/integrating-services.md +++ b/docs/development/integrating-services.md @@ -1,6 +1,6 @@ # Integrating Services -One of the primary roles of Alliance Auth is integrating with external services in order to authenticate and manage users. This is achieved through the use of service modules. +One of the primary roles of Alliance Auth is integrating with external services in order to authenticate and manage users. This is achieved through the use of service modules. ## The Service Module @@ -29,8 +29,8 @@ The architecture looks something like this: | | AllianceAuth - - + + Where: Module --▶ Dependency/Import @@ -46,10 +46,10 @@ In order to integrate with Alliance Auth service modules must provide a `service This would register the ExampleService class which would need to be a subclass of `services.hooks.ServiceHook`. - + ```eval_rst .. important:: - The hook **MUST** be registered in `yourservice.auth_hooks` along with any other hooks you are registering for Alliance Auth. + The hook **MUST** be registered in ``yourservice.auth_hooks`` along with any other hooks you are registering for Alliance Auth. ``` @@ -97,7 +97,7 @@ Functions: Internal name of the module, should be unique amongst modules. #### self.urlpatterns -You should define all of your service urls internally, usually in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns. +You should define all of your service URLs internally, usually in `urls.py`. Then you can import them and set `self.urlpatterns` to your defined urlpatterns. from . import urls ... @@ -105,7 +105,7 @@ You should define all of your service urls internally, usually in `urls.py`. The def __init__(self): ... self.urlpatterns = urls.urlpatterns - + All of your apps defined urlpatterns will then be included in the `URLconf` when the core application starts. #### self.service_ctrl_template @@ -147,7 +147,7 @@ If this function is defined, an admin action will be registered on the Django Us #### update_groups `def update_groups(self, user):` -Update the users group membership. The `user` parameter should be a Django User object. +Update the users group membership. The `user` parameter should be a Django User object. When this is called the service should determine the groups the user is a member of and synchronise the group membership with the external service. If you service does not support groups then you are not required to define this. If this function is defined, an admin action will be registered on the Django Users view, allowing admins to manually trigger this action for one or many users. The hook will trigger this action user by user, so you won't have to manage a list of users. @@ -176,7 +176,7 @@ Should the service be shown for the given `user` with the given `state`? The `us Usually you wont need to override this function. For more information see the [render_service_ctrl](#render-service-ctrl) section. - + #### render_service_ctrl `def render_services_ctrl(self, request):` @@ -221,7 +221,7 @@ Most services will survive with the default template. If, however, you require e If you services needs cannot be satisfied by the Service Control row, you are free to specify extra hooks by subclassing or instantiating the `services.hooks.MenuItemHook` class. -For more information see the [Menu Hooks](menu-hooks.md) page. +For more information see the [Menu Hooks](menu-hooks.md) page. ### The Service Manager diff --git a/docs/features/autogroups.md b/docs/features/autogroups.md index c502fcde..bc2dc537 100644 --- a/docs/features/autogroups.md +++ b/docs/features/autogroups.md @@ -24,12 +24,12 @@ When you create an autogroup config you will be given the following options: After creating a group you wont be able to change the Corp and Alliance group prefixes, name source and the replace spaces settings. Make sure you configure these the way you want before creating the config. If you need to change these you will have to create a new autogroup config. ``` -- States selects which states will be added to automatic corp/alliance groups +- States selects which states will be added to automatic Corp/Alliance groups -- Corp/Alliance groups checkbox toggles corp/alliance autogroups on or off for this config. +- Corp/Alliance groups checkbox toggles Corp/Alliance autogroups on or off for this config. -- Corp/Alliance group prefix sets the prefix for the group name, e.g. if your corp was called `MyCorp` and your prefix was `Corp `, your autogroup name would be created as `Corp MyCorp`. This field accepts leading/trailing spaces. +- Corp/Alliance group prefix sets the prefix for the group name, e.g. if your Corp was called `MyCorp` and your prefix was `Corp `, your autogroup name would be created as `Corp MyCorp`. This field accepts leading/trailing spaces. -- Corp/Alliance name source sets the source of the corp/alliance name used in creating the group name. Currently the options are Full name and Ticker. +- Corp/Alliance name source sets the source of the Corp/Alliance name used in creating the group name. Currently the options are Full name and Ticker. - Replace spaces allows you to replace spaces in the autogroup name with the value in the Replace spaces with field. This can be blank. diff --git a/docs/features/corpstats.md b/docs/features/corpstats.md index 18beb86b..525de2e5 100644 --- a/docs/features/corpstats.md +++ b/docs/features/corpstats.md @@ -1,6 +1,6 @@ # Corp Stats -This module is used to check the registration status of corp members and to determine character relationships, being mains or alts. +This module is used to check the registration status of Corp members and to determine character relationships, being mains or alts. ## Installation @@ -10,13 +10,13 @@ Add `'allianceauth.corputils',` to your `INSTALLED_APPS` list in your auth proje ## Creating a Corp Stats -Upon initial install, nothing will be visible. For every corp, a model will have to be created before data can be viewed. +Upon initial install, nothing will be visible. For every Corp, a model will have to be created before data can be viewed. ![nothing is visible](/_static/images/features/corpstats/blank_header.png) If you are a superuser, the add button will be immediate visible to you. If not, your user account requires the `add_corpstats` permission. -Corp Stats requires an EVE SSO token to access data from the EVE Swagger Interface. Upon pressing the Add button, you will be prompted to authenticated. Please select the character who is in the corp you want data for. +Corp Stats requires an EVE SSO token to access data from the EVE Swagger Interface. Upon pressing the Add button, you will be prompted to authenticated. Please select the character who is in the Corporation you want data for. ![authorize from the EVE site](/_static/images/features/corpstats/eve_sso_authorization.png) @@ -33,7 +33,7 @@ If it fails an error message will be displayed. ![navigation bar](/_static/images/features/corpstats/navbar.png) -This bar contains a dropdown menu of all available corps. If the user has the `add_corpstats` permission, a button to add a Corp Stats will be shown. +This bar contains a dropdown menu of all available Corporations. If the user has the `add_corpstats` permission, a button to add a Corp Stats will be shown. On the right of this bar is a search field. Press enter to search. It checks all characters in all Corp Stats you have view permission to and returns search results. @@ -41,7 +41,7 @@ On the right of this bar is a search field. Press enter to search. It checks all ![last update and update button](/_static/images/features/corpstats/last_update.png) -An update can be performed immediately by pressing the update button. Anyone who can view the Corp Stats can update it. +An update can be performed immediately by pressing the update button. Anyone who can view the Corp Stats can update it. ### Character Lists @@ -60,15 +60,15 @@ Each view contains a sortable and searchable table. The number of listings shown ![main list](/_static/images/features/corpstats/main_list.png) -This list contains all main characters in registered in the selected corporation and their alts. Each character has a link to [zKillboard](https://zkillboard.com). +This list contains all main characters in registered in the selected Corporation and their alts. Each character has a link to [zKillboard](https://zkillboard.com). #### Member List ![member list](/_static/images/features/corpstats/member_list.png) -The list contains all characters in the corp. Red backgrounds means they are not registered in auth. A link to [zKillboard](https://zkillboard.com) is present for all characters. -If registered, the character will also have a main character, main corporation, and main alliance field. +The list contains all characters in the Corporation. Red backgrounds means they are not registered in auth. A link to [zKillboard](https://zkillboard.com) is present for all characters. +If registered, the character will also have a main character, main Corporation, and main Alliance field. #### Unregistered List @@ -80,7 +80,7 @@ This list contains all characters not registered on auth. Each character has a l ![search results](/_static/images/features/corpstats/search_view.png) -This view is essentially the same as the Corp Stats page, but not specific to a single corp. +This view is essentially the same as the Corp Stats page, but not specific to a single Corporation. The search query is visible in the search box. Characters from all Corp Stats to which the user has view access will be displayed. APIs respect permissions. @@ -108,7 +108,7 @@ To use this feature, users will require some of the following: ``` -Users who add a Corp Stats with their token will be granted permissions to view it regardless of the above permissions. View permissions are interpreted in the "OR" sense: a user can view their corp's Corp Stats without the `view_corp_corpstats` permission if they have the `view_alliance_corpstats` permission, same idea for their state. Note that these evaluate against the user's main character. +Users who add a Corp Stats with their token will be granted permissions to view it regardless of the above permissions. View permissions are interpreted in the "OR" sense: a user can view their Corporations's Corp Stats without the `view_corp_corpstats` permission if they have the `view_alliance_corpstats` permission, same idea for their state. Note that these evaluate against the user's main character. ## Automatic Updating By default Corp Stats are only updated on demand. If you want to automatically refresh on a schedule, add an entry to your project's settings file: @@ -126,12 +126,12 @@ Adjust the crontab as desired. >Unrecognized corporation. Please ensure it is a member of the alliance or a blue. -Corp Stats can only be created for corporations who have a model in the database. These only exist for tenant corps, +Corp Stats can only be created for Corporations who have a model in the database. These only exist for tenant corps, corps of tenant alliances, blue corps, and members of blue alliances. >Selected corp already has a statistics module. -Only one Corp Stats may exist at a time for a given corporation. +Only one Corp Stats may exist at a time for a given Corporation. >Failed to gather corporation statistics with selected token. @@ -147,7 +147,7 @@ This occurs when the SSO token is invalid, which can occur when deleted by the u >CorpStats for (corp name) cannot update with your ESI token as you have left corp. -The SSO token's character is no longer in the corp which the Corp Stats is for, and therefore membership data cannot be retrieved. +The SSO token's character is no longer in the Corporation which the Corp Stats is for, and therefore membership data cannot be retrieved. >HTTPForbidden diff --git a/docs/features/fleetup.md b/docs/features/fleetup.md index 4fd36a8b..a846fb0b 100644 --- a/docs/features/fleetup.md +++ b/docs/features/fleetup.md @@ -11,8 +11,8 @@ Additional settings are required. Append the following settings to the end of yo FLEETUP_API_ID = '' # The API id from http://fleet-up.com/Api/MyKeys FLEETUP_GROUP_ID = '' # The id of the group you want to pull data from, see http://fleet-up.com/Api/Endpoints#groups_mygroupmemberships -Once filled out restart gunicorn and celery. +Once filled out restart Gunicorn and Celery. ## Permissions -The Fleetup module is only visible to users with the `auth | user | view_fleeup` permission. \ No newline at end of file +The Fleetup module is only visible to users with the `auth | user | view_fleeup` permission. diff --git a/docs/features/hrapplications.md b/docs/features/hrapplications.md index a498b19f..b1c4a90d 100644 --- a/docs/features/hrapplications.md +++ b/docs/features/hrapplications.md @@ -8,15 +8,15 @@ Add `'allianceauth.hrapplications',` to your `INSTALLED_APPS` list in your auth ### Creating Forms -The most common task is creating ApplicationForm models for corps. Only when such models exist will a corp be listed as a choice for applicants. This occurs in the django admin site, so only staff have access. +The most common task is creating ApplicationForm models for corps. Only when such models exist will a Corporation be listed as a choice for applicants. This occurs in the Django admin site, so only staff have access. The first step is to create questions. This is achieved by creating ApplicationQuestion models, one for each question. Titles are not unique. -Next step is to create the actual ApplicationForm model. It requires an existing EveCorporationInfo model to which it will belong. It also requires the selection of questions. ApplicationForm models are unique per corp: only one may exist for any given corp concurrently. +Next step is to create the actual ApplicationForm model. It requires an existing EveCorporationInfo model to which it will belong. It also requires the selection of questions. ApplicationForm models are unique per Corporation: only one may exist for any given Corporation concurrently. You can adjust these questions at any time. This is the preferred method of modifying the form: deleting and recreating will cascade the deletion to all received applications from this form which is usually not intended. -Once completed the corp will be available to receive applications. +Once completed the Corporation will be available to receive applications. ### Reviewing Applications @@ -63,11 +63,11 @@ This is the model representation of a question. It contains a title, and a field ### ApplicationForm -This is the template for an application. It points at a corp, with only one form allowed per corp. It also points at ApplicationQuestion models. When a user creates an application, they will be prompted with each question the form includes at the given time. Modifying questions in a form after it has been created will not be reflected in existing applications, so it's perfectly fine to adjust them as you see fit. Changing corps however is not advisable, as existing applications will point at the wrong corp after they've been submitted, confusing reviewers. +This is the template for an application. It points at a Corporation, with only one form allowed per Corporation. It also points at ApplicationQuestion models. When a user creates an application, they will be prompted with each question the form includes at the given time. Modifying questions in a form after it has been created will not be reflected in existing applications, so it's perfectly fine to adjust them as you see fit. Changing Corporations however is not advisable, as existing applications will point at the wrong Corporation after they've been submitted, confusing reviewers. ### Application -This is the model representation of a completed application. It references an ApplicationForm from which it was spawned which is where the corp specificity comes from. It points at a user, contains info regarding its reviewer, and has a status. Shortcut properties also provide the applicant's main character, the applicant's APIs, and a string representation of the reviewer (for cases when the reviewer doesn't have a main character or the model gets deleted). +This is the model representation of a completed application. It references an ApplicationForm from which it was spawned which is where the Corporation specificity comes from. It points at a user, contains info regarding its reviewer, and has a status. Shortcut properties also provide the applicant's main character, the applicant's APIs, and a string representation of the reviewer (for cases when the reviewer doesn't have a main character or the model gets deleted). ### ApplicationResponse @@ -81,7 +81,7 @@ This is a reviewer's comment on an application. Points at the application, point ### No corps accepting applications -Ensure there are ApplicationForm models in the admin site. Ensure the user does not already have an application to these corps. If the users wishes to re-apply they must first delete their completed application +Ensure there are ApplicationForm models in the admin site. Ensure the user does not already have an application to these Corporations. If the users wishes to re-apply they must first delete their completed application ### Reviewer unable to complete application diff --git a/docs/features/nameformats.md b/docs/features/nameformats.md index e26837a9..ed466d93 100644 --- a/docs/features/nameformats.md +++ b/docs/features/nameformats.md @@ -5,7 +5,7 @@ New in 2.0 ``` -Each service's username or nickname, depending on which the service supports, can be customised through the use of the Name Formatter Config provided the service supports custom formats. This config can be found in the admin panel under **Services -> Name format config** +Each service's username or nickname, depending on which the service supports, can be customised through the use of the Name Formatter config provided the service supports custom formats. This config can be found in the admin panel under **Services -> Name format config** Currently the following services support custom name formats: @@ -37,7 +37,7 @@ Currently the following services support custom name formats: ```eval_rst .. note:: - It's important to note here, before we get into what you can do with a name formatter, that before the generated name is passed off to the service to create an account it will be sanitised to remove characters (the letters and numbers etc) that the service cannot support. This means that, despite what you configured, the service may display something different. It is up to you to test your formatter and understand how your format may be disrupted by a certain services sanitisation function. + It's important to note here, before we get into what you can do with a name formatter, that before the generated name is passed off to the service to create an account it will be sanitised to remove characters (the letters and numbers etc.) that the service cannot support. This means that, despite what you configured, the service may display something different. It is up to you to test your formatter and understand how your format may be disrupted by a certain services sanitisation function. ``` ## Available format data @@ -53,14 +53,14 @@ The following fields are available from a users account and main character: - `alliance_id` - `alliance_name` - `alliance_ticker` - - `alliance_or_corp_name` (defaults to corp name if there is no alliance) - - `alliance_or_corp_ticker` (defaults to corp ticker if there is no alliance) + - `alliance_or_corp_name` (defaults to Corporation name if there is no Alliance) + - `alliance_or_corp_ticker` (defaults to Corporation ticker if there is no Alliance) ## Building a formatter string The name formatter uses the advanced string formatting specified by [PEP-3101](https://www.python.org/dev/peps/pep-3101/). Anything supported by this specification is supported in a name formatter. -A more digestable documentation of string formatting in Python is available on the [PyFormat](https://pyformat.info/) website. +A more digestible documentation of string formatting in Python is available on the [PyFormat](https://pyformat.info/) website. Some examples of strings you could use: ```eval_rst diff --git a/docs/features/permissions_tool.md b/docs/features/permissions_tool.md index b5f32309..e65c40d1 100644 --- a/docs/features/permissions_tool.md +++ b/docs/features/permissions_tool.md @@ -1,10 +1,5 @@ # Permissions Auditing -```eval_rst -.. note:: - New in 1.15 -``` - Access to most of Alliance Auth's features are controlled by Django's permissions system. In order to help you secure your services, Alliance Auth provides a permissions auditing tool. ## Installation diff --git a/docs/features/states.md b/docs/features/states.md index 82aa56c8..5aa29c39 100644 --- a/docs/features/states.md +++ b/docs/features/states.md @@ -2,7 +2,7 @@ ## Overview -In Alliance Auth v1 admins were able to define which corporations and alliances were to be considered "members" with full permissions and "blues" with restricted permissions. The state system is the replacement for these static definitions: admins can now create as many states as desired, as well as extend membership to specific characters. +In Alliance Auth v1 admins were able to define which Corporations and Alliances were to be considered "members" with full permissions and "blues" with restricted permissions. The state system is the replacement for these static definitions: admins can now create as many states as desired, as well as extend membership to specific characters. ## Creating a State States are created through your installation's admin site. Upon install three states are created for you: `Member`, `Blue`, and `Guest`. New ones can be created like any other Django model by users with the appropriate permission (`authentication | state | Can add state`) or superusers. @@ -27,10 +27,10 @@ Checking this box means this state is available to all users. There isn't much u This lets you select which characters the state is available to. Characters can be added by selecting the green plus icon. ### Member Corporations -This lets you select which corporations the state is available to. Corporations can be added by selecting the green plus icon. +This lets you select which Corporations the state is available to. Corporations can be added by selecting the green plus icon. ### Member Alliances -This lets you select which alliances the state is available to. Alliances can be added by selecting the gree plus icon. +This lets you select which Alliances the state is available to. Alliances can be added by selecting the green plus icon. ## Determining a User's State States are mutually exclusive, meaning a user can only be in one at a time. @@ -45,5 +45,3 @@ Assigned states are visible in the `Users` section of the `Authentication` admin If no states are available to a user's main character, or their account has been deactivated, they are assigned to a catch-all `Guest` state. This state cannot be deleted nor can its name be changed. The `Guest` state allows permissions to be granted to users who would otherwise not get any. For example access to public services can be granted by giving the `Guest` state a service access permission. - - \ No newline at end of file diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index 44273ced..fb25aea5 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -2,12 +2,12 @@ ```eval_rst .. tip:: - If you are uncomfortable with linux permissions follow the steps below as the root user. Some commands do not behave the same when run with sudo. + If you are uncomfortable with Linux permissions follow the steps below as the root user. Some commands do not behave the same when run with sudo. ``` ## Dependencies -Alliance Auth can be installed on any operating system. Dependencies are provided below for two of the most popular server platforms, Ubuntu and CentOS. To install on your favourite flavour of linux, identify and install equivalent packages to the ones listed here. +Alliance Auth can be installed on any operating system. Dependencies are provided below for two of the most popular server platforms, Ubuntu and CentOS. To install on your favourite flavour of Linux, identify and install equivalent packages to the ones listed here. ```eval_rst .. hint:: @@ -31,7 +31,7 @@ CentOS: ### Database -It's recommended to use a database service instead of sqlite. Many options are available, but this guide will use MariaDB. +It's recommended to use a database service instead of SQLite. Many options are available, but this guide will use MariaDB. Ubuntu: @@ -60,7 +60,7 @@ CentOS: ```eval_rst .. important:: - CentOS: Make sure redis is running before continuing. :: + CentOS: Make sure Redis is running before continuing. :: systemctl enable redis.service systemctl start redis.service @@ -104,7 +104,7 @@ Create a Python virtual environment and put it somewhere convenient (e.g. `/home ```eval_rst .. warning:: - The python3 command may not be available on all installations. Try a specific version such as python3.6 if this is the case. + The python3 command may not be available on all installations. Try a specific version such as ``python3.6`` if this is the case. ``` ```eval_rst @@ -117,14 +117,14 @@ Activate the virtualenv using `source /home/allianceserver/venv/auth/bin/activat ```eval_rst .. hint:: - Each time you come to do maintenance on your Alliance Auth installation, you should activate your virtual environment first. When finished, deactivate it with the 'deactivate' command. + Each time you come to do maintenance on your Alliance Auth installation, you should activate your virtual environment first. When finished, deactivate it with the ``deactivate`` command. ``` Ensure wheel is available with `pip install wheel` before continuing. ### Alliance Auth Project -You can install the library using `pip install allianceauth`. This will install Alliance Auth and all its python dependencies. You should also install gunicorn with `pip install gunicorn` before proceeding. +You can install the library using `pip install allianceauth`. This will install Alliance Auth and all its python dependencies. You should also install Gunicorn with `pip install gunicorn` before proceeding. Now you need to create the application that will run the Alliance Auth install. Ensure you are in the allianceserver home directory by issuing `cd /home/allianceserver`. @@ -153,13 +153,13 @@ And finally ensure the allianceserver user has read/write permissions to this di ### Gunicorn -To run the auth website a [WSGI Server](https://www.fullstackpython.com/wsgi-servers.html) is required. [Gunicorn](http://gunicorn.org/) is highly recommended for its ease of configuring. It can be manually run with `gunicorn myauth.wsgi` or automatically run using supervisor. +To run the auth website a [WSGI Server](https://www.fullstackpython.com/wsgi-servers.html) is required. [Gunicorn](http://gunicorn.org/) is highly recommended for its ease of configuring. It can be manually run from within your `myauth` base directory with `gunicorn --bind 0.0.0.0 myauth.wsgi` or automatically run using Supervisor. The default configuration is good enough for most installations. Additional information is available in the [gunicorn](gunicorn.md) doc. ### Supervisor -[Supervisor](http://supervisord.org/) is a process watchdog service: it makes sure other processes are started automatically and kept running. It can be used to automatically start the WSGI server and celery workers for background tasks. Installation varies by OS: +[Supervisor](http://supervisord.org/) is a process watchdog service: it makes sure other processes are started automatically and kept running. It can be used to automatically start the WSGI server and Celery workers for background tasks. Installation varies by OS: Ubuntu: @@ -171,7 +171,7 @@ CentOS: systemctl enable supervisord.service systemctl start supervisord.service -Once installed it needs a configuration file to know which processes to watch. Your Alliance Auth project comes with a ready-to-use template which will ensure the celery workers, celery task scheduler and gunicorn are all running. +Once installed it needs a configuration file to know which processes to watch. Your Alliance Auth project comes with a ready-to-use template which will ensure the Celery workers, Celery task scheduler and Gunicorn are all running. Ubuntu: @@ -187,7 +187,7 @@ You can check the status of the processes with `supervisorctl status`. Logs from ```eval_rst .. note:: - Any time the code or your settings change you'll need to restart gunicorn and celery. :: + Any time the code or your settings change you'll need to restart Gunicorn and Celery. :: supervisorctl restart myauth: ``` @@ -214,4 +214,4 @@ Some releases come with changes to settings: update your project's settings with Some releases come with new or changed models. Update your database to reflect this with `python /home/allianceserver/myauth/manage.py migrate`. -Always restart celery and gunicorn after updating. +Always restart Celery and Gunicorn after updating. diff --git a/docs/installation/auth/apache.md b/docs/installation/auth/apache.md index fbd315f8..9be3aa4e 100644 --- a/docs/installation/auth/apache.md +++ b/docs/installation/auth/apache.md @@ -24,7 +24,7 @@ Apache needs to be able to read the folder containing your auth project's static Apache serves sites through defined virtual hosts. These are located in `/etc/apache2/sites-available/` on Ubuntu and `/etc/httpd/conf.d/httpd.conf` on CentOS. -A virtual host for auth need only proxy requests to your WSGI server (gunicorn if you followed the install guide) and serve static files. Examples can be found below. Create your config in its own file eg `myauth.conf`. +A virtual host for auth need only proxy requests to your WSGI server (Gunicorn if you followed the install guide) and serve static files. Examples can be found below. Create your config in its own file e.g. `myauth.conf` ### Ubuntu @@ -34,7 +34,7 @@ To proxy and modify headers a few mods need to be enabled. a2enmod proxy_http a2enmod headers -Create a new config file for auth eg `/etc/apache2/sites-available/myauth.conf` and fill out the virtual host configuration. To enable your config use `a2ensite myauth.conf` and then reload apache with `service apache2 reload`. +Create a new config file for auth e.g. `/etc/apache2/sites-available/myauth.conf` and fill out the virtual host configuration. To enable your config use `a2ensite myauth.conf` and then reload apache with `service apache2 reload`. ### CentOS diff --git a/docs/installation/auth/gunicorn.md b/docs/installation/auth/gunicorn.md index 623922a6..46b2db37 100644 --- a/docs/installation/auth/gunicorn.md +++ b/docs/installation/auth/gunicorn.md @@ -10,12 +10,12 @@ Check out the full [Gunicorn docs](http://docs.gunicorn.org/en/latest/index.html ```eval_rst .. note:: - If you're using a virtual environment (and I would encourage you to do so when hosting Alliance Auth), activate it now. `source /path/to/venv/bin/activate`. + If you're using a virtual environment, activate it now. ``source /path/to/venv/bin/activate``. ``` Install Gunicorn using pip, `pip install gunicorn`. -In your `myauth` base directory, try running `gunicorn --bind 0.0.0.0:8000 myauth.wsgi`. You should be able to browse to http://yourserver:8000 and see your Alliance Auth installation running. Images and styling will be missing, but dont worry, your web server will provide them. +In your `myauth` base directory, try running `gunicorn --bind 0.0.0.0:8000 myauth.wsgi`. You should be able to browse to http://yourserver:8000 and see your Alliance Auth installation running. Images and styling will be missing, but don't worry, your web server will provide them. Once you validate its running, you can kill the process with Ctrl+C and continue. diff --git a/docs/installation/auth/nginx.md b/docs/installation/auth/nginx.md index 487db888..15b2bcd5 100644 --- a/docs/installation/auth/nginx.md +++ b/docs/installation/auth/nginx.md @@ -1,6 +1,6 @@ # NGINX -## Overivew +## Overview Nginx (engine x) is a HTTP server known for its high performance, stability, simple configuration, and low resource consumption. Unlike traditional servers (i.e. Apache), Nginx doesn't rely on threads to serve requests, rather using an asynchronous event driven approach which permits predictable resource usage and performance under load. @@ -12,7 +12,7 @@ You can read more about NGINX on the [NGINX wiki](https://www.nginx.com/resource If you're converting from Apache, here are some things to consider. -Nginx is lightweight for a reason. It doesn't try to do everything internally and instead concentrates on just being a good HTTP server. This means that, unlike Apache, it wont automatically run PHP scripts via mod_php and doesn't have an internal WSGI server like mod_wsgi. That doesn't mean that it can't, just that it relies on external processes to run these instead. This might be good or bad depending on your outlook. It's good because it allows you to segment your applications, restarting Alliance Auth wont impact your PHP applications. On the other hand it means more config and more management of services. For some people it will be worth it, for others losing the centralised nature of Apache may not be worth it. +Nginx is lightweight for a reason. It doesn't try to do everything internally and instead concentrates on just being a good HTTP server. This means that, unlike Apache, it won't automatically run PHP scripts via mod_php and doesn't have an internal WSGI server like mod_wsgi. That doesn't mean that it can't, just that it relies on external processes to run these instead. This might be good or bad depending on your outlook. It's good because it allows you to segment your applications, restarting Alliance Auth wont impact your PHP applications. On the other hand it means more config and more management of services. For some people it will be worth it, for others losing the centralised nature of Apache may not be worth it. ```eval_rst +-----------+----------------------------------------+ @@ -25,13 +25,20 @@ Nginx is lightweight for a reason. It doesn't try to do everything internally an ``` -Your .htaccess files wont work. Nginx has a separate way of managing access to folders via the server config. Everything you can do with htaccess files you can do with Nginx config. [Read more on the Nginx wiki](https://www.nginx.com/resources/wiki/start/topics/examples/likeapache-htaccess/) +Your .htaccess files won't work. Nginx has a separate way of managing access to folders via the server config. Everything you can do with htaccess files you can do with Nginx config. [Read more on the Nginx wiki](https://www.nginx.com/resources/wiki/start/topics/examples/likeapache-htaccess/) ## Setting up Nginx Install Nginx via your preferred package manager or other method. If you need help just search, there are plenty of guides on installing Nginx out there. -Nginx needs to be able to read the folder containing your auth project's static files. On Ubuntu: `chown -R nginx:nginx /var/www/myauth/static`, and on CentOS: `chown -R nginx:nginx /var/www/myauth/static` +Nginx needs to be able to read the folder containing your auth project's static files. `chown -R nginx:nginx /var/www/myauth/static`. + +```eval_rst +.. tip:: + Some specific distros may use www-data:www-data instead of nginx:nginx, causing static files (images, stylesheets etc) not to appear. You can confirm what user Nginx will run under by checking either its base config file `/etc/nginx/nginx.conf` for the "user" setting, or once Nginx has started `ps aux | grep nginx`. + Adjust your chown commands to the correct user if needed. +.. +``` You will need to have [Gunicorn](gunicorn.md) or some other WSGI server setup for hosting Alliance Auth. @@ -57,7 +64,7 @@ server { location = /favicon.ico { access_log off; log_not_found off; } - location /static/ { + location /static { alias /var/www/myauth/static; autoindex off; } diff --git a/docs/installation/services/discord.md b/docs/installation/services/discord.md index 09e8562c..5f3ad75e 100644 --- a/docs/installation/services/discord.md +++ b/docs/installation/services/discord.md @@ -1,6 +1,8 @@ # Discord ## Overview -Discord is a web-based instant messaging client with voice. Kind of like teamspeak meets slack meets skype. It also has a standalone app for phones and desktop. +Discord is a web-based instant messaging client with voice. Kind of like TeamSpeak meets Slack meets Skype. It also has a standalone app for phones and desktop. + +Discord is very popular amongst ad-hoc small groups and larger organizations seeking a modern technology. Alternative voice communications should be investigated for larger than small-medium groups for more advanced features. ## Setup @@ -25,11 +27,7 @@ Navigate to the [Discord site](https://discordapp.com/) and register an account, On the left side of the screen you’ll see a circle with a plus sign. This is the button to create a new server. Go ahead and do that, naming it something obvious. -Now retrieve the server ID from the URL of the page you’re on. The ID is the first of the very long numbers. For instance my testing server’s url look like: - - https://discordapp.com/channels/120631096835571712/120631096835571712 - -with a server ID of `120631096835571712` +Now retrieve the server ID [following this procedure.](https://support.discordapp.com/hc/en-us/articles/206346498-Where-can-I-find-my-User-Server-Message-ID-) Update your auth project's settings file, inputting the server ID as `DISCORD_GUILD_ID` @@ -49,22 +47,22 @@ Update your auth project's settings file with these pieces of information from t - From the App Bot Users panel, `DISCORD_BOT_TOKEN` is the Token ### Preparing Auth -Before continuing it is essential to run migrations and restart gunicorn and celery. +Before continuing it is essential to run migrations and restart Gunicorn and Celery. ### Adding a Bot to the Server -Once created, navigate to the services page of your AllianceAuth install as the superuser account. At the top there is a big green button labelled Link Discord Server. Click it, then from the drop down select the server you created, and then Authorize. +Once created, navigate to the services page of your Alliance Auth install as the superuser account. At the top there is a big green button labelled Link Discord Server. Click it, then from the drop down select the server you created, and then Authorize. This adds a new user to your Discord server with a `BOT` tag, and a new role with the same name as your Discord application. Don't touch either of these. If for some reason the bot loses permissions or is removed from the server, click this button again. -To manage roles, this bot role must be at the top of the hierarchy. Edit your Discord server, roles, and click and drag the role with the same name as your application to the top of the list. This role must stay at the top of the list for the bot to work. Finally, the owner of the bot account must enable 2 Factor Authentication (this is required from discord for kicking and modifying member roles). If you are unsure what 2FA is or how to set it up, refer to [this support page](https://support.discordapp.com/hc/en-us/articles/219576828). It is also recommended to force 2fa on your server (this forces any admins or moderators to have 2fa enabled to perform similar functions on discord). +To manage roles, this bot role must be at the top of the hierarchy. Edit your Discord server, roles, and click and drag the role with the same name as your application to the top of the list. This role must stay at the top of the list for the bot to work. Finally, the owner of the bot account must enable 2 Factor Authentication (this is required from Discord for kicking and modifying member roles). If you are unsure what 2FA is or how to set it up, refer to [this support page](https://support.discordapp.com/hc/en-us/articles/219576828). It is also recommended to force 2FA on your server (this forces any admins or moderators to have 2fa enabled to perform similar functions on discord). Note that the bot will never appear online as it does not participate in chat channels. ### Linking Accounts -Instead of the usual account creation procedure, for Discord to work we need to link accounts to AllianceAuth. When attempting to enable the Discord service, users are redirected to the official Discord site to authenticate. They will need to create an account if they don't have one prior to continuing. Upon authorization, users are redirected back to AllianceAuth with an OAuth code which is used to join the Discord server. +Instead of the usual account creation procedure, for Discord to work we need to link accounts to Alliance Auth. When attempting to enable the Discord service, users are redirected to the official Discord site to authenticate. They will need to create an account if they don't have one prior to continuing. Upon authorization, users are redirected back to Alliance Auth with an OAuth code which is used to join the Discord server. ### Syncing Nicknames If you want users to have their Discord nickname changed to their in-game character name, set `DISCORD_SYNC_NAMES` to `True` ## Managing Roles -Once users link their accounts you’ll notice Roles get populated on Discord. These are the equivalent to Groups on every other service. The default permissions should be enough for members to chat and use comms. Add more permissions to the roles as desired through the server management window. +Once users link their accounts you’ll notice Roles get populated on Discord. These are the equivalent to Groups on every other service. The default permissions should be enough for members to use text and audio communications. Add more permissions to the roles as desired through the server management window. diff --git a/docs/installation/services/discourse.md b/docs/installation/services/discourse.md index 39ead8de..6a844f2f 100644 --- a/docs/installation/services/discourse.md +++ b/docs/installation/services/discourse.md @@ -2,9 +2,9 @@ ## Prepare Your Settings In your auth project's settings file, do the following: - - Add `'allianceauth.services.modules.discourse',` to your `INSTALLED_APPS` list + - Add `'allianceauth.services.modules.discourse',` to your `INSTALLED_APPS` list - Append the following to your local.py settings file: - + # Discourse Configuration DISCOURSE_URL = '' @@ -35,7 +35,7 @@ Change the following: - `DISCOUSE_HOSTNAME` should be `discourse.example.com` or something similar. - Everything with `SMTP` depends on your mail settings. [There are plenty of free email services online recommended by Discourse](https://github.com/discourse/discourse/blob/master/docs/INSTALL-email.md#recommended-email-providers-for-discourse) if you haven't set one up for auth already. -To install behind apache/nginx, look for this section: +To install behind Apache/Nginx, look for this section: ... ## which TCP/IP ports should this container expose? @@ -61,7 +61,7 @@ Uncomment this line: DOCKER_OPTS="--dns 8.8.8.8 --dns 8.8.4.4" -Restart docker: +Restart Docker: service docker restart @@ -74,7 +74,7 @@ Now build: You will need to configure your web server to proxy requests to Discourse. -A minimal apache config might look like: +A minimal Apache config might look like: ServerName discourse.example.com @@ -82,7 +82,7 @@ A minimal apache config might look like: ProxyPassReverse / http://0.0.0.0:7890/ -A minimal nginx config might look like: +A minimal Nginx config might look like: server { listen 80; @@ -122,4 +122,4 @@ Navigate to `discourse.example.com` and log in. Back to the admin site, scroll d Save, now set `DISCOURSE_SSO_SECRET` in your auth project's settings file to the secure key you just put in Discourse. -Finally run migrations and restart gunicorn and celery. +Finally run migrations and restart Gunicorn and Celery. diff --git a/docs/installation/services/market.md b/docs/installation/services/market.md index 932f1849..d38d229b 100644 --- a/docs/installation/services/market.md +++ b/docs/installation/services/market.md @@ -1,7 +1,7 @@ # Alliance Market ## Dependencies -Alliance Market requires php installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. +Alliance Market requires PHP installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. ## Prepare Your Settings In your auth project's settings file, do the following: @@ -21,14 +21,14 @@ In your auth project's settings file, do the following: } ## Setup Alliance Market -Alliance Market needs a database. Create one in mysql. Default name is `alliance_market`: +Alliance Market needs a database. Create one in MySQL/MariaDB. Default name is `alliance_market`: mysql -u root -p create database alliance_market; grant all privileges on alliance_market . * to 'allianceserver'@'localhost'; exit; -To clone the repo, install packages: +Install required packages to clone the repository: sudo apt-get install mercurial meld @@ -36,7 +36,7 @@ Change to the web folder: cd /var/www -Now clone the repo +Now clone the repository sudo hg clone https://bitbucket.org/krojew/evernus-alliance-market @@ -51,7 +51,7 @@ Change ownership to apache sudo chown -R www-data:www-data evernus-alliance-market -Enter +Enter directory cd evernus-alliance-market @@ -67,7 +67,7 @@ Edit, changing the following: - `database_name` to `alliance_market` - `database_user` to your MySQL user (usually `allianceserver`) - `database_password` to your MySQL user password - - email settings, eg gmail + - email settings, eg Gmail/Mailgun etc. Edit `app/config/config.yml` and add the following: @@ -101,7 +101,7 @@ Install SDE: Configure your web server to serve alliance market. -A minimal apache config might look like: +A minimal Apache config might look like: ServerName market.example.com @@ -113,7 +113,7 @@ A minimal apache config might look like: -A minimal nginx config might look like: +A minimal Nginx config might look like: server { listen 80; @@ -121,11 +121,11 @@ A minimal nginx config might look like: root /var/www/evernus-alliance-market/web; index app.php; access_log /var/logs/market.access.log; - + location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/tmp/php.socket; - fastcgi_index index.php; + fastcgi_index app.php; fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; include fastcgi_params; } @@ -141,4 +141,4 @@ Add a user account through auth, then make it a superuser: Now edit your auth project's settings file and fill in the web URL to your market as well as the database details. -Finally run migrations and restart gunicorn and celery. \ No newline at end of file +Finally run migrations and restart Gunicorn and Celery. diff --git a/docs/installation/services/mumble.md b/docs/installation/services/mumble.md index 861ca797..f628d42a 100644 --- a/docs/installation/services/mumble.md +++ b/docs/installation/services/mumble.md @@ -4,13 +4,13 @@ In your auth project's settings file, do the following: - Add `'allianceauth.services.modules.mumble',` to your `INSTALLED_APPS` list - Append the following to your local.py settings file: - + # Mumble Configuration MUMBLE_URL = "" ## Overview -Mumble is a free voice chat server. While not as flashy as teamspeak, it has all the functionality and is easier to customize. And is better. I may be slightly biased. +Mumble is a free voice chat server. While not as flashy as TeamSpeak, it has all the functionality and is easier to customize. And is better. I may be slightly biased. ## Dependencies The mumble server package can be retrieved from a repository we need to add, mumble/release. @@ -35,11 +35,11 @@ REQUIRED: To enable the ICE authenticator, edit the following: - `icesecretwrite=MY_CLEVER_PASSWORD`, obviously choosing a secure password -By default mumble operates on sqlite which is fine, but slower than a dedicated MySQL server. To customize the database, edit the following: +By default mumble operates on SQLite which is fine, but slower than a dedicated MySQL server. To customize the database, edit the following: - uncomment the database line, and change it to `database=alliance_mumble` - `dbDriver=QMYSQL` - - `dbUsername=allianceserver` or whatever you called the AllianceAuth MySQL user + - `dbUsername=allianceserver` or whatever you called the Alliance Auth MySQL user - `dbPassword=` that user’s password - `dbPort=3306` - `dbPrefix=murmur_` @@ -48,7 +48,7 @@ To name your root channel, uncomment and set `registerName=` to whatever cool na Save and close the file (control + O, control + X). -To get mumble superuser account credentials, run the following: +To get Mumble superuser account credentials, run the following: sudo dpkg-reconfigure mumble-server @@ -85,9 +85,9 @@ The authenticator needs to be running 24/7 to validate users on Mumble. You shou Note that groups will only be created on Mumble automatically when a user joins who is in the group. ## Making and Managing Channels -ACL is really above the scope of this guide. Once AllianceAuth creates your groups, go ahead and follow one of the wonderful web guides available on how to set up channel ACL properly. +ACL is really above the scope of this guide. Once Alliance Auth creates your groups, go ahead and follow one of the wonderful web guides available on how to set up channel ACL properly. ## Prepare Auth In your project's settings file, set `MUMBLE_URL` to the public address of your mumble server. Do not include any leading `http://` or `mumble://`. -Run migrations and restart gunicorn and celery to complete setup. \ No newline at end of file +Run migrations and restart Gunicorn and Celery to complete setup. diff --git a/docs/installation/services/openfire.md b/docs/installation/services/openfire.md index e11be192..2190862b 100644 --- a/docs/installation/services/openfire.md +++ b/docs/installation/services/openfire.md @@ -1,6 +1,6 @@ # Openfire - Openfire is a jabber (XMPP) server. + Openfire is a Jabber (XMPP) server. ## Prepare Your Settings - Add `'allianceauth.services.modules.openfire',` to your `INSTALLED_APPS` list @@ -18,7 +18,7 @@ BROADCAST_SERVICE_NAME = "broadcast" ## Overview -Openfire is a java-based xmpp server (jabber). +Openfire is a Java-based XMPP server (Jabber). ## Dependencies One additional package is required - openjdk8 @@ -35,24 +35,30 @@ CentOS: ## Setup ### Download Installer -Openfire is not available through repositories so we need to get a debian from the developer. +Openfire is not available through repositories so we need to get a package from the developer. -On your PC, naviage to the [Ignite Realtime downloads section](https://www.igniterealtime.org/downloads/index.jsp), and under Openfire select Linux, click on the debian file (2nd from bottom of list, ends with .deb). +On your PC, navigate to the [Ignite Realtime downloads section](https://www.igniterealtime.org/downloads/index.jsp), and under Openfire select Linux, click on the Ubuntu: Debian package (second from bottom of list, ends with .deb) or CentOS: RPM Package (no JRE bundled, as we have installed it on the host) -Retrieve the file location by copying the url from the “click here” link. +Retrieve the file location by copying the URL from the “click here” link, depending on your browser you may have a Copy Link or similar option in your right click menu. In the console, ensure you’re in your user’s home directory: `cd ~` Now download the package. Replace the link below with the link you got earlier. - wget https://www.igniterealtime.org/downloadServlet?filename=openfire/openfire_4.1.1_all.deb + wget https://www.igniterealtime.org/downloadServlet?filename=openfire/openfire_4.2.3_all.deb -Now install from the debian. Replace the filename with your file name (the last part of the download url is the file name) +Now install from the package. Replace the filename with your filename (the last part of the download URL is the file name) - sudo dpkg -i openfire_4.1.1_all.deb +Ubuntu: + + sudo dpkg -i openfire_4.2.3_all.deb + +CentOS: + + sudo yum install -y openfire-4.2.3-1.noarch.rpm ### Create Database -Performance is best when working from a SQL database. If you installed MySQL or MariaDB alongside your auth project, go ahead and create a database for openfire: +Performance is best when working from a SQL database. If you installed MySQL or MariaDB alongside your auth project, go ahead and create a database for Openfire: mysql -u root -p create database alliance_jabber; @@ -62,7 +68,7 @@ Performance is best when working from a SQL database. If you installed MySQL or ### Web Configuration The remainder of the setup occurs through Openfire’s web interface. Navigate to http://example.com:9090, or if you’re behind CloudFlare, go straight to your server’s IP:9090. -Select your language. I sure hope it’s english if you’re reading this guide. +Select your language. I sure hope it’s English if you’re reading this guide. Under Server Settings, set the Domain to `example.com` replacing it with your actual domain. Don’t touch the rest. @@ -100,7 +106,7 @@ Select `Enabled`, and `Secret Key Auth`. Update your auth project's settings wit Navigate to the `Users/Groups` tab and select `Create New User` from the left navigation bar. -Pick a username (eg `broadcast`) and password for your ping user. Enter these in your auth project's settings file as `BROADCAST_USER` and `BROADCAST_USER_PASSWORD`. Note that `BROADCAST_USER` needs to be in the format `user@example.com` matching your jabber server name. Press `Create User` to save this user. +Pick a username (e.g. `broadcast`) and password for your ping user. Enter these in your auth project's settings file as `BROADCAST_USER` and `BROADCAST_USER_PASSWORD`. Note that `BROADCAST_USER` needs to be in the format `user@example.com` matching your jabber server name. Press `Create User` to save this user. Broadcasting requires a plugin. Navigate to the `plugins` tab, press the green plus for the `Broadcast` plugin. @@ -117,7 +123,7 @@ If you have troubles getting broadcasts to work, you can try setting the optiona ### Preparing Auth -Once all settings are entered, run migrations and restart gunicorn and celery. +Once all settings are entered, run migrations and restart Gunicorn and Celery. ### Group Chat Channels are available which function like a chat room. Access can be controlled either by password or ACL (not unlike mumble). diff --git a/docs/installation/services/permissions.md b/docs/installation/services/permissions.md index 9778c7e7..847bc364 100644 --- a/docs/installation/services/permissions.md +++ b/docs/installation/services/permissions.md @@ -1,14 +1,10 @@ # Service Permissions -```eval_rst -.. note:: - New in 1.15 -``` In the past, access to services was dictated by a list of settings in `settings.py`, granting access to each particular service for Members and/or Blues. This meant that granting access to a service was very broad and rigidly structured around these two states. ## Permissions based access -Instead of granting access to services by the previous rigid structure, access to services is now granted by the built in Django permissions system. This means that service access can be more granular, allowing only certain groups, for instance Corp CEOs, or even individual user access to each enabled service. +Instead of granting access to services by the previous rigid structure, access to services is now granted by the built in Django permissions system. This means that service access can be more granular, allowing only certain states, certain groups, for instance Corp CEOs, or even individual user access to each enabled service. ```eval_rst .. important:: @@ -34,4 +30,4 @@ If a user no longer has permission to access the service when this permissions c ### Disabling user accounts -When you unset a user as active in the admin panel, all of that users service accounts will be immediately disabled or removed. This is due to the built in behaviour of Djangos permissions system, which will return False for all permissions if a users account is disabled, regardless of their actual permissions state. +When you unset a user as active in the admin panel, all of that users service accounts will be immediately disabled or removed. This is due to the built in behaviour of the Django permissions system, which will return False for all permissions if a users account is disabled, regardless of their actual permissions state. diff --git a/docs/installation/services/phpbb3.md b/docs/installation/services/phpbb3.md index d44c8bb8..d31167cd 100644 --- a/docs/installation/services/phpbb3.md +++ b/docs/installation/services/phpbb3.md @@ -1,12 +1,10 @@ # phpBB3 - and run migrations before continuing with this guide to ensure the service is installed. - ## Overview -phpBB is a free php-based forum. It’s the default forum for AllianceAuth. +phpBB is a free PHP-based forum. ## Dependencies -PHPBB3 requires php installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. See [the official guide](https://www.phpbb.com/community/docs/INSTALL.html) for php package requirements. +phpBB3 requires PHP installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. See [the official guide](https://www.phpbb.com/community/docs/INSTALL.html) for PHP package requirements. ## Prepare Your Settings In your auth project's settings file, do the following: @@ -27,7 +25,7 @@ In your auth project's settings file, do the following: ## Setup ### Prepare the Database -Create a database to install phpbb3 in. +Create a database to install phpBB3 in. mysql -u root -p create database alliance_forum; @@ -36,12 +34,12 @@ Create a database to install phpbb3 in. Edit your auth project's settings file and fill out the `DATABASES['phpbb3']` part. -### Download Phpbb3 -phpBB is available as a zip from their website. Navigate to the website’s [downloads section](https://www.phpbb.com/downloads/) using your PC browser and copy the URL for the latest version zip. +### Download phpbb3 +phpBB is available as a zip from their website. Navigate to the website’s [downloads section](https://www.phpbb.com/downloads/) using your PC browser and copy the URL for the latest version zip. Depending on your browser you may have a Copy Link or similar option in your right click menu. In the console, navigate to your user’s home directory: `cd ~` -Now download using wget, replacing the url with the url for the package you just retrieved +Now download using wget, replacing the URL with the URL for the package you just retrieved wget https://www.phpbb.com/files/release/phpBB-3.2.2.zip @@ -55,12 +53,19 @@ Now we need to move this to our web directory. Usually `/var/www/forums`. The web server needs read/write permission to this folder - sudo chown -R www-data:www-data /var/www/forums +Apache: `sudo chown -R www-data:www-data /var/www/forums` +Nginx: `sudo chown -R nginx:nginx /var/www/forums` + +```eval_rst +.. tip:: + Nginx: Some distributions use the ``www-data:www-data`` user:group instead of ``nginx:nginx``. If you run into problems with permissions try it instead. +.. +``` ### Configuring Web Server You will need to configure you web server to serve PHPBB3 before proceeding with installation. -A minimal apache config file might look like: +A minimal Apache config file might look like: ServerName forums.example.com @@ -71,7 +76,7 @@ A minimal apache config file might look like: -A minimal nginx config file might look like: +A minimal Nginx config file might look like: server { listen 80; @@ -79,16 +84,16 @@ A minimal nginx config file might look like: root /var/www/forums; index index.php; access_log /var/logs/forums.access.log; - + location ~ /(config\.php|common\.php|cache|files|images/avatars/upload|includes|store) { deny all; return 403; } - + location ~* \.(gif|jpe?g|png|css)$ { expires 30d; } - + location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/tmp/php.socket; @@ -117,7 +122,7 @@ Under Database Settings, set the following: If you use a table prefix other than the standard `phpbb_` you need to add an additional setting to your auth project's settings file, `PHPBB3_TABLE_PREFIX = ''`, and enter the prefix. -You should see `Succesful Connection` and proceed. +You should see `Successful Connection` and proceed. Enter administrator credentials on the next page. @@ -140,4 +145,4 @@ You can allow members to overwrite the portrait with a custom image if desired. ![location of change avatar setting](/_static/images/installation/services/phpbb3/avatar_permissions.png) ### Prepare Auth -Once settings have been configured, run migrations and restart gunicorn and celery. +Once settings have been configured, run migrations and restart Gunicorn and Celery. diff --git a/docs/installation/services/smf.md b/docs/installation/services/smf.md index 9044449c..6a0d34ce 100644 --- a/docs/installation/services/smf.md +++ b/docs/installation/services/smf.md @@ -1,15 +1,15 @@ # SMF ## Overview -SMF is a free php-based forum. +SMF is a free PHP-based forum. ## Dependencies -SMF requires php installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. More details can be found in the [SMF requirements page.](https://download.simplemachines.org/requirements.php) +SMF requires PHP installed in your web server. Apache has `mod_php`, NGINX requires `php-fpm`. More details can be found in the [SMF requirements page.](https://download.simplemachines.org/requirements.php) ## Prepare Your Settings In your auth project's settings file, do the following: - Add `'allianceauth.services.modules.smf',` to your `INSTALLED_APPS` list - - Append the following to the bottom of the settings file: + - Append the following to the bottom of the settings file: # SMF Configuration @@ -25,15 +25,16 @@ In your auth project's settings file, do the following: ## Setup ### Download SMF -Using your browser, you can download the latest version of SMF to your desktop computer. All SMF downloads can be found at SMF Downloads. The latest recommended version will always be available at http://www.simplemachines.org/download/index.php/latest/install/. +Using your browser, you can download the latest version of SMF to your desktop computer. All SMF downloads can be found at SMF Downloads. The latest recommended version will always be available at http://www.simplemachines.org/download/index.php/latest/install/. Retrieve the file location from the hyperlinked box icon for the zip full install, depending on your browser you may have a Copy Link or similar option in your right click menu. -Download using wget, replacing the url with the url for the package you just retrieved - wget http://download.simplemachines.org/index.php?thanks;filename=smf_2-0-13_install.zip +Download using wget, replacing the URL with the URL for the package you just retrieved + + wget https://download.simplemachines.org/index.php?thanks;filename=smf_2-0-15_install.zip This needs to be unpackaged. Unzip it, replacing the file name with that of the file you just downloaded - unzip smf_2-0-13_install.zip + unzip smf_2-0-15_install.zip Now we need to move this to our web directory. Usually `/var/www/forums`. @@ -41,7 +42,14 @@ Now we need to move this to our web directory. Usually `/var/www/forums`. The web server needs read/write permission to this folder - sudo chown -R www-data:www-data /var/www/forums +Apache: `sudo chown -R www-data:www-data /var/www/forums` +Nginx: `sudo chown -R nginx:nginx /var/www/forums` + +```eval_rst +.. tip:: + Nginx: Some distributions use the ``www-data:www-data`` user:group instead of ``nginx:nginx``. If you run into problems with permissions try it instead. +.. +``` ### Database Preparation SMF needs a database. Create one: @@ -56,7 +64,7 @@ Enter the database information into the `DATABASES['smf']` section of your auth ### Web Server Configuration Your web server needs to be configured to serve Alliance Market. -A minimal apache config might look like: +A minimal Apache config might look like: ServerName forums.example.com @@ -66,7 +74,7 @@ A minimal apache config might look like: -A minimal nginx config might look like: +A minimal Nginx config might look like: server { listen 80; @@ -74,7 +82,7 @@ A minimal nginx config might look like: root /var/www/forums; index app.php; access_log /var/logs/forums.access.log; - + location ~ \.php$ { try_files $uri =404; fastcgi_pass unix:/tmp/php.socket; @@ -86,9 +94,6 @@ A minimal nginx config might look like: Enter the web address to your forums into the `SMF_URL` setting in your auth project's settings file. -### Preparing Auth -Once settings are entered, apply migrations and restart gunicorn and celery. - ### Web Install Navigate to your forums address where you will be presented with an installer. @@ -106,4 +111,7 @@ Under Database Settings, set the following: If you use a table prefix other than the standard `smf_` you need to add an additional setting to your auth project's settings file, `SMF_TABLE_PREFIX = ''`, and enter the prefix. -Follow the directions in the installer. \ No newline at end of file +Follow the directions in the installer. + +### Preparing Auth +Once settings are entered, apply migrations and restart Gunicorn and Celery. diff --git a/docs/installation/services/teamspeak3.md b/docs/installation/services/teamspeak3.md index aec183eb..91fbeeae 100644 --- a/docs/installation/services/teamspeak3.md +++ b/docs/installation/services/teamspeak3.md @@ -1,7 +1,7 @@ -# Teamspeak 3 +# TeamSpeak 3 ## Overview -Teamspeak3 is the most popular VOIP program for gamers. +TeamSpeak3 is the most popular VOIP program for gamers. But have you considered using Mumble? Not only is it free, but it has features and performance far superior to Teamspeak3. @@ -12,7 +12,7 @@ Sticking with TS3? Alright, I tried. In your auth project's settings file, do the following: - Add `'allianceauth.services.modules.teamspeak3',` to your `INSTALLED_APPS` list - Append the following to the bottom of the settings file: - + # Teamspeak3 Configuration TEAMSPEAK3_SERVER_IP = '127.0.0.1' @@ -21,25 +21,25 @@ In your auth project's settings file, do the following: TEAMSPEAK3_SERVERQUERY_PASSWORD = '' TEAMSPEAK3_VIRTUAL_SERVER = 1 TEAMSPEAK3_PUBLIC_URL = '' - + CELERYBEAT_SCHEDULE['run_ts3_group_update'] = { 'task': 'allianceauth.services.modules.teamspeak3.tasks.run_ts3_group_update', 'schedule': crontab(minute='*/30'), } ### Download Installer -To install we need a copy of the server. You can find the latest version from [this dl server](http://dl.4players.de/ts/releases/) (I’d recommed getting the latest stable version – find this version number from the [TeamSpeak site](https://www.teamspeak.com/downloads#)). Be sure to get a link to the linux version. +To install we need a copy of the server. You can find the latest version from [this dl server](http://dl.4players.de/ts/releases/) (I’d recommend getting the latest stable version – find this version number from the [TeamSpeak site](https://www.teamspeak.com/downloads#)). Be sure to get a link to the Linux version. Download the server, replacing the link with the link you got earlier. - http://dl.4players.de/ts/releases/3.1.0/teamspeak3-server_linux_amd64-3.1.0.tar.bz2 + http://dl.4players.de/ts/releases/3.1.1/teamspeak3-server_linux_amd64-3.1.1.tar.bz2 Now we need to extract the file. tar -xf teamspeak3-server_linux_amd64-3.1.0.tar.bz2 ### Create User -Teamspeak needs its own user. +TeamSpeak needs its own user. sudo adduser --disabled-login teamspeak @@ -50,7 +50,7 @@ Now we move the server binary somewhere more accessible and change its ownership sudo chown -R teamspeak:teamspeak /usr/local/teamspeak ### Startup -Now we generate a startup script so teamspeak comes up with the server. +Now we generate a startup script so TeamSpeak comes up with the server. sudo ln -s /usr/local/teamspeak/ts3server_startscript.sh /etc/init.d/teamspeak sudo update-rc.d teamspeak defaults @@ -68,12 +68,12 @@ Edit the settings you added to your auth project's settings file earlier, enteri - `TEAMSPEAK3_SERVERQUERY_USER` is `loginname` from that block of text it just spat out (usually `serveradmin`) - `TEAMSPEAK3_SERVERQUERY_PASSWORD` is `password` from that block of text it just spat out - `TEAMSPEAK_VIRTUAL_SERVER` is the virtual server ID of the server to be managed - it will only ever not be 1 if your server is hosted by a professional company - - `TEAMSPEAK3_PUBLIC_URL` is the public address of your teamspeak server. Do not include any leading http:// or teamspeak:// + - `TEAMSPEAK3_PUBLIC_URL` is the public address of your TeamSpeak server. Do not include any leading http:// or teamspeak:// -Once settings are entered, run migrations and restart gunicorn and celery. +Once settings are entered, run migrations and restart Gunicorn and Celery. ### Generate User Account -And now we can generate ourselves a user account. Navigate to the services in AllianceAuth for your user account and press the checkmark for TeamSpeak 3. +And now we can generate ourselves a user account. Navigate to the services in Alliance Auth for your user account and press the checkmark for TeamSpeak 3. Click the URL provided to automatically connect to our server. It will prompt you to redeem the serveradmin token, enter the `token` from startup. diff --git a/docs/installation/services/xenforo.md b/docs/installation/services/xenforo.md index 462a34ea..0db52585 100644 --- a/docs/installation/services/xenforo.md +++ b/docs/installation/services/xenforo.md @@ -37,4 +37,4 @@ The settings you created earlier now need to be filled out. `XENFORO_API_KEY` is the API key value you set earlier. -Once these are entered, run migrations and restart gunicorn and celery. \ No newline at end of file +Once these are entered, run migrations and restart Gunicorn and Celery. diff --git a/docs/maintenance/project.md b/docs/maintenance/project.md index d9bf2bea..79214a92 100644 --- a/docs/maintenance/project.md +++ b/docs/maintenance/project.md @@ -8,15 +8,15 @@ When installing Alliance Auth you are instructed to run the `allianceauth start` The first folder created is the root directory of your auth project. This folder contains: - the `manage.py` management script used to interact with Django - - a preconfigured `supervisor.conf` supervisor config for running celery (and optionally gunicorn) automatically + - a preconfigured `supervisor.conf` Supervisor config for running Celery (and optionally Gunicorn) automatically - a `log` folder which contains log files generated by Alliance Auth ### The myauth Subfolder -Within your auth project root folder is another folder of the same name (a quirk of django project structures). This folder contains: - - a celery app definition in `celery.py` for registering tasks with the background workers +Within your auth project root folder is another folder of the same name (a quirk of Django project structures). This folder contains: + - a Celery app definition in `celery.py` for registering tasks with the background workers - a web server gateway interface script `wsgi.py` for processing web requests - - the root url config `urls.py` which Django uses to direct requests to the appropriate view + - the root URL config `urls.py` which Django uses to direct requests to the appropriate view There are also two subfolders for `static` and `templates` which allow adding new content and overriding default content shipped with Alliance Auth or Django. @@ -35,15 +35,15 @@ The local settings file is referred to as "your auth project's settings file" an Your auth project comes with four log file definitions by default. These are created in the `myauth/log/` folder at runtime. - `allianceauth.log` contains all `INFO` level and above logging messages from Alliance Auth. This is useful for tracking who is making changes to the site, what is happening to users, and debugging any errors that may occur. - - `worker.log` contains logging messages from the celery background task workers. This is useful for monitoring background processes such as group syncing to services. + - `worker.log` contains logging messages from the Celery background task workers. This is useful for monitoring background processes such as group syncing to services. - `beat.log` contains logging messages from the background task scheduler. This is of limited use unless the scheduler isn't starting. - - `gunicorn.log` contains logging messages from gunicorn workers. This contains all web-sourced messages found in `allianceauth.log` as well as runtime errors from the workers themselves. + - `gunicorn.log` contains logging messages from Gunicorn workers. This contains all web-sourced messages found in `allianceauth.log` as well as runtime errors from the workers themselves. When asking for assistance with your auth project be sure to first read the logs, and share any relevant entries. ## Custom Static and Templates -Within your auth project exists two folders named `static` and `templates`. These are used by Django for rendering web pages. Static refers to content Django does not need to parse before displaying, such as CSS styling or images. When running via a WSGI worker such as gunicorn static files are copied to a location for the web server to read from. Templates are always read from the template folders, rendered with additional context from a view function, and then displayed to the user. +Within your auth project exists two folders named `static` and `templates`. These are used by Django for rendering web pages. Static refers to content Django does not need to parse before displaying, such as CSS styling or images. When running via a WSGI worker such as Gunicorn static files are copied to a location for the web server to read from. Templates are always read from the template folders, rendered with additional context from a view function, and then displayed to the user. You can add extra static or templates by putting files in these folders. Note that changes to static requires running the `python manage.py collectstatic` command to copy to the web server directory. diff --git a/docs/maintenance/troubleshooting.md b/docs/maintenance/troubleshooting.md index 42ef7394..9737e991 100644 --- a/docs/maintenance/troubleshooting.md +++ b/docs/maintenance/troubleshooting.md @@ -20,7 +20,7 @@ It's probably a permissions issue. Ensure your current user can write to the vir ### Failed to configure log handler -Make sure the log directory is write-able by the allianceserver user: `chmown -R allianceserver:allianceserver /path/to/myauth/log/`, then restart the auth supervisor processes. +Make sure the log directory is writeable by the allianceserver user: `chmown -R allianceserver:allianceserver /path/to/myauth/log/`, then restart the auth supervisor processes. ### Groups aren't syncing to services @@ -40,3 +40,11 @@ Now start the worker again with `supervisorctl start myauth:worker` ### Proxy timeout when entering email address This usually indicates an issue with your email settings. Ensure these are correct and your email server/service is properly configured. + +### No images are available to users accessing the website + +This is due to a permissions mismatch, check the setup guide for your web server. Additionally ensure the user who owns /var/www/myauth/static is the same user as running your webserver, as this can be non-standard. + +### Unable to execute 'gunicorn myauth.wsgi' or ImportError: No module named 'myauth.wsgi' + +Gunicorn needs to have context for its running location, `/home/alllianceserver/myauth/gunicorn myauth.wsgi` will not work, instead `cd /home/alllianceserver/myauth` then `gunicorn myauth.wsgi` is needed to boot Gunicorn. This is handled in the Supervisor config, but this may be encountered running Gunicorn manually for testing. From b8a2d65a1d7d4f32be4b23e7ebb11460b2216eae Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 17 Apr 2018 20:59:08 -0400 Subject: [PATCH 31/47] Create a separate doc page for upgrade from v1. --- docs/installation/auth/allianceauth.md | 6 -- docs/installation/auth/index.md | 1 + docs/installation/auth/upgradev1.md | 76 ++++++++++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) create mode 100644 docs/installation/auth/upgradev1.md diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index fb25aea5..222feff0 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -76,12 +76,6 @@ Alliance Auth needs a MySQL user account and database. Open an SQL shell with `m Close the SQL shell and secure your database server with the `mysql_secure_installation` command. -If you're updating from v1, populate this database with a copy of the data from your v1 database. - - mysqldump -u root -p v1_database_name_here | mysql -u root -p alliance_auth - -Note this command will prompt you for the root password twice. - ## Auth Install ### User Account diff --git a/docs/installation/auth/index.md b/docs/installation/auth/index.md index 0a64cb11..f3ef2e17 100644 --- a/docs/installation/auth/index.md +++ b/docs/installation/auth/index.md @@ -4,6 +4,7 @@ .. toctree:: allianceauth + upgradev1 gunicorn nginx apache diff --git a/docs/installation/auth/upgradev1.md b/docs/installation/auth/upgradev1.md new file mode 100644 index 00000000..2a663a93 --- /dev/null +++ b/docs/installation/auth/upgradev1.md @@ -0,0 +1,76 @@ +# Upgrading from v1.15 + +It's possible to preserve a v1 install's database and migrate it to v2. This will retain all service accounts, user accounts with their main character, but will purge API keys and alts. + +## Preparing to Upgrade + +Ensure your auth install is at least version 1.15 - it's preferred to be on v1.15.8 but any v1.15.x should work. If not, update following normal procedures and ensure you run migrations. + +If you will not be using any apps (or they have been removed in v2) clear their database tables before beginning the v2 install procedure. From within your v1 install, `python manage.py migrate APPNAME zero`, replacing `APPNAME` with the appropriate name. + +It's strongly encouraged to perform the upgrade with a **copy** of the original v1 database. If something goes wrong this allows you to roll back and try again. This can be achieved with: + + mysqldump -u root -p v1_database_name_here | mysql -u root -p v2_database_name_here + +Note this command will prompt you for the root password twice. + +Alliance Auth v2 requires Python 3.4 or newer. If this is not available on your system be sure to install the [dependencies listed.](allianceauth.md#python) + +## Installation + +Follow the [normal install procedures for Alliance Auth v2](allianceauth.md). If you're coming from v1 you likely already have the user account and dependencies. + +When configuring settings, ensure to enter the database information relating to the copy you made of the v1 database, but **do not run migrations**. See below before continuing. + +Do not start Celery yet. + +## Migration + +During the upgrade process a migration is run which ports data to the new system. Its behaviour can be configured through some optional settings. + +### User Accounts + +A database migration is included to port users to the new SSO-based login system. It will automatically assign states and main characters. + +Password for non-staff accounts are destroyed so they cannot be used to log in - only SSO is available for regular members. Staff can login using username and password through the admin site or by SSO. + +### EVE Characters + +Character ownership is now tracked by periodically validating a refreshable SSO token. When migrating from v1 not all users may have such a refreshable token: to allow these users to retain their existing user account their main character is set to the one present in v1, bypassing this validation mechanism. + +It is essential to get your users to log in via SSO soon after migration to ensure their accounts are managed properly. Any user who does not log in will not lose their main character under any circumstance, even character sale. During a sale characters are transferred to an NPC corp - get celery tasks running within 24 hours of migration so that during a sale the user loses their state (and hence permissions). This can be an issue if you've granted service access permissions to a user or their groups: it's strongly encouraged to grant service access by state from now on. + +Because character ownership is tracked separately of main character it is not possible to preserve alts unless a refreshable SSO token is present for them. + +### Members and Blues + +The new [state system](../../features/states.md) allows configuring dynamic membership states through the admin page. Unfortunately if you make a change after migrating it will immediately assess user states and see that no one should be a member. You can add additional settings to your auth project's settings file to generate the member and blue states as you have them defined in v1: + - `ALLIANCE_IDS = []` a list of member alliance IDs + - `CORP_IDS = []` a list of member corporation IDs + - `BLUE_ALLIANCE_IDS = []` a list of blue alliance IDs + - `BLUE_CORP_IDS = []` a list of blue corporation IDs + +Put comma-separated IDs into the brackets and the migration will create states with the members and blues you had before. This will prevent unexpected state purging when you edit states via the admin site. + +### Default Groups + +If you used member/blue group names other than the standard "Member" and "Blue" you can enter settings to have the member/blue states created through this migration take these names. + - `DEFAULT_AUTH_GROUP = ""` the desired name of the "Member" state + - `DEFAULT_BLUE_GROUP = ""` the desired name of the "Blue" state + +Any permissions assigned to these groups will be copied to the state replacing them. Because these groups are no longer managed they pose a security risk and so are deleted at the end of the migration automatically. + + +## Validating Upgrade + +Before starting the Celery workers it's a good idea to validate the states were created and assigned correctly. Any mistakes now will trigger deletion of service accounts: if celery workers aren't running these tasks aren't yet processed. States can be checked through the admin site. + +The site (and not Celery) can be started with `supervisorctl start myauth:gunicorn`. Then navigate to the admin site and log in with your v1 username and password. States and User Profiles can be found under the Authentication app. + +Once you have confirmed states are correctly configured (or adjusted them as needed) clear the Celery task queue: from within your auth project folder, `celery -A myauth purge` + +It should now be safe to bring workers online (`supervisorctl start myauth:`) and invite users to use the site. + +## Help + +If something goes wrong during the migration reach out for help on [Gitter](https://gitter.im/R4stl1n/allianceauth) or open an [issue](https://github.com/allianceauth/allianceauth/issues). \ No newline at end of file From 2cd8188ffb0addfcf31b0b83260dceb3c5b448d8 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 17 Apr 2018 21:37:15 -0400 Subject: [PATCH 32/47] Include a functional market nginx config. Addresses #1021 Thanks @mmolitor87 --- docs/installation/services/market.md | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/docs/installation/services/market.md b/docs/installation/services/market.md index d38d229b..0a478b66 100644 --- a/docs/installation/services/market.md +++ b/docs/installation/services/market.md @@ -122,12 +122,29 @@ A minimal Nginx config might look like: index app.php; access_log /var/logs/market.access.log; - location ~ \.php$ { - try_files $uri =404; - fastcgi_pass unix:/tmp/php.socket; - fastcgi_index app.php; - fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + # strip app.php/ prefix if it is present + rewrite ^/app\.php/?(.*)$ /$1 permanent; + + location / { + index app.php; + try_files $uri @rewriteapp; + } + + location @rewriteapp { + rewrite ^(.*)$ /app.php/$1 last; + } + + # pass the PHP scripts to FastCGI server from upstream phpfcgi + location ~ ^/(app|app_dev|config)\.php(/|$) { + fastcgi_pass 127.0.0.1:9000; + fastcgi_split_path_info ^(.+\.php)(/.*)$; include fastcgi_params; + fastcgi_param SCRIPT_FILENAME $document_root$fastcgi_script_name; + fastcgi_param HTTPS off; + } + + location ~ /\.ht { + deny all; } } From a350e175c78dcf13e754d3bdb978e2afb80840a1 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Wed, 18 Apr 2018 20:47:44 -0400 Subject: [PATCH 33/47] Update to latest ESI routes. --- allianceauth/corputils/models.py | 3 +-- allianceauth/corputils/swagger.json | 2 +- allianceauth/corputils/tests.py | 4 ++-- allianceauth/eveonline/providers.py | 13 +++++++------ allianceauth/eveonline/swagger.json | 2 +- allianceauth/fleetactivitytracking/swagger.json | 2 +- 6 files changed, 13 insertions(+), 13 deletions(-) diff --git a/allianceauth/corputils/models.py b/allianceauth/corputils/models.py index 42251a80..4d3dc2da 100644 --- a/allianceauth/corputils/models.py +++ b/allianceauth/corputils/models.py @@ -40,9 +40,8 @@ class CorpStats(models.Model): c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH) assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[ 'corporation_id'] == int(self.corp.corporation_id) - members = c.Corporation.get_corporations_corporation_id_members( + member_ids = c.Corporation.get_corporations_corporation_id_members( corporation_id=self.corp.corporation_id).result() - member_ids = [m['character_id'] for m in members] # requesting too many ids per call results in a HTTP400 # the swagger spec doesn't have a maxItems count diff --git a/allianceauth/corputils/swagger.json b/allianceauth/corputils/swagger.json index 09fbfec7..b58538e5 100644 --- a/allianceauth/corputils/swagger.json +++ b/allianceauth/corputils/swagger.json @@ -1 +1 @@ -{"info": {"version": "0.5.5", "title": "EVE Swagger Interface", "description": "An OpenAPI for EVE Online"}, "swagger": "2.0", "basePath": "/", "produces": ["application/json"], "schemes": ["https"], "securityDefinitions": {"evesso": {"scopes": {"esi-killmails.read_killmails.v1": "EVE SSO scope esi-killmails.read_killmails.v1", "esi-characters.read_fatigue.v1": "EVE SSO scope esi-characters.read_fatigue.v1", "esi-corporations.read_structures.v1": "EVE SSO scope esi-corporations.read_structures.v1", "esi-mail.read_mail.v1": "EVE SSO scope esi-mail.read_mail.v1", "esi-assets.read_assets.v1": "EVE SSO scope esi-assets.read_assets.v1", "esi-skills.read_skillqueue.v1": "EVE SSO scope esi-skills.read_skillqueue.v1", "esi-calendar.read_calendar_events.v1": "EVE SSO scope esi-calendar.read_calendar_events.v1", "esi-assets.read_corporation_assets.v1": "EVE SSO scope esi-assets.read_corporation_assets.v1", "esi-characters.read_standings.v1": "EVE SSO scope esi-characters.read_standings.v1", "esi-characters.read_opportunities.v1": "EVE SSO scope esi-characters.read_opportunities.v1", "esi-location.read_ship_type.v1": "EVE SSO scope esi-location.read_ship_type.v1", "esi-corporations.read_contacts.v1": "EVE SSO scope esi-corporations.read_contacts.v1", "esi-ui.open_window.v1": "EVE SSO scope esi-ui.open_window.v1", "esi-calendar.respond_calendar_events.v1": "EVE SSO scope esi-calendar.respond_calendar_events.v1", "esi-search.search_structures.v1": "EVE SSO scope esi-search.search_structures.v1", "esi-skills.read_skills.v1": "EVE SSO scope esi-skills.read_skills.v1", "esi-characters.read_chat_channels.v1": "EVE SSO scope esi-characters.read_chat_channels.v1", "esi-universe.read_structures.v1": "EVE SSO scope esi-universe.read_structures.v1", "esi-characters.read_corporation_roles.v1": "EVE SSO scope esi-characters.read_corporation_roles.v1", "esi-characters.read_medals.v1": "EVE SSO scope esi-characters.read_medals.v1", "esi-characters.read_contacts.v1": "EVE SSO scope esi-characters.read_contacts.v1", "esi-fittings.read_fittings.v1": "EVE SSO scope esi-fittings.read_fittings.v1", "esi-characters.read_agents_research.v1": "EVE SSO scope esi-characters.read_agents_research.v1", "esi-wallet.read_character_wallet.v1": "EVE SSO scope esi-wallet.read_character_wallet.v1", "esi-bookmarks.read_character_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_character_bookmarks.v1", "esi-location.read_location.v1": "EVE SSO scope esi-location.read_location.v1", "esi-characters.write_contacts.v1": "EVE SSO scope esi-characters.write_contacts.v1", "esi-ui.write_waypoint.v1": "EVE SSO scope esi-ui.write_waypoint.v1", "esi-markets.structure_markets.v1": "EVE SSO scope esi-markets.structure_markets.v1", "esi-corporations.read_corporation_membership.v1": "EVE SSO scope esi-corporations.read_corporation_membership.v1", "esi-markets.read_character_orders.v1": "EVE SSO scope esi-markets.read_character_orders.v1", "esi-wallet.read_corporation_wallets.v1": "EVE SSO scope esi-wallet.read_corporation_wallets.v1", "esi-characters.read_blueprints.v1": "EVE SSO scope esi-characters.read_blueprints.v1", "esi-characters.read_loyalty.v1": "EVE SSO scope esi-characters.read_loyalty.v1", "esi-characters.read_notifications.v1": "EVE SSO scope esi-characters.read_notifications.v1", "esi-clones.read_implants.v1": "EVE SSO scope esi-clones.read_implants.v1", "esi-corporations.write_structures.v1": "EVE SSO scope esi-corporations.write_structures.v1", "esi-planets.manage_planets.v1": "EVE SSO scope esi-planets.manage_planets.v1", "esi-industry.read_character_jobs.v1": "EVE SSO scope esi-industry.read_character_jobs.v1", "esi-mail.send_mail.v1": "EVE SSO scope esi-mail.send_mail.v1", "esi-corporations.read_divisions.v1": "EVE SSO scope esi-corporations.read_divisions.v1", "esi-fittings.write_fittings.v1": "EVE SSO scope esi-fittings.write_fittings.v1", "esi-corporations.track_members.v1": "EVE SSO scope esi-corporations.track_members.v1", "esi-contracts.read_character_contracts.v1": "EVE SSO scope esi-contracts.read_character_contracts.v1", "esi-killmails.read_corporation_killmails.v1": "EVE SSO scope esi-killmails.read_corporation_killmails.v1", "esi-fleets.write_fleet.v1": "EVE SSO scope esi-fleets.write_fleet.v1", "esi-location.read_online.v1": "EVE SSO scope esi-location.read_online.v1", "esi-clones.read_clones.v1": "EVE SSO scope esi-clones.read_clones.v1", "esi-fleets.read_fleet.v1": "EVE SSO scope esi-fleets.read_fleet.v1", "esi-mail.organize_mail.v1": "EVE SSO scope esi-mail.organize_mail.v1"}, "flow": "implicit", "type": "oauth2", "authorizationUrl": "https://login.eveonline.com/oauth/authorize"}}, "parameters": {"X-User-Agent": {"type": "string", "in": "header", "name": "X-User-Agent", "description": "Client identifier, takes precedence over User-Agent"}, "character_id": {"in": "path", "description": "An EVE character ID", "name": "character_id", "required": true, "format": "int32", "type": "integer"}, "language": {"in": "query", "description": "Language to use in the response", "name": "language", "type": "string", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"], "default": "en-us"}, "corporation_id": {"in": "path", "description": "An EVE corporation ID", "name": "corporation_id", "required": true, "format": "int32", "type": "integer"}, "datasource": {"in": "query", "description": "The server name you would like data from", "name": "datasource", "type": "string", "enum": ["tranquility", "singularity"], "default": "tranquility"}, "page": {"in": "query", "description": "Which page of results to return", "name": "page", "type": "integer", "format": "int32", "default": 1}, "token": {"type": "string", "in": "query", "name": "token", "description": "Access token to use if unable to set a header"}, "alliance_id": {"in": "path", "description": "An EVE alliance ID", "name": "alliance_id", "required": true, "format": "int32", "type": "integer"}, "user_agent": {"type": "string", "in": "query", "name": "user_agent", "description": "Client identifier, takes precedence over headers"}}, "paths": {"/v2/corporations/{corporation_id}/members/": {"get": {"x-alternate-versions": ["legacy", "v2"], "summary": "Get corporation members", "tags": ["Corporation"], "responses": {"500": {"examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error"}, "200": {"examples": {"application/json": [{"character_id": 90000001}, {"character_id": 90000002}]}, "schema": {"title": "get_corporations_corporation_id_members_ok", "maxItems": 12601, "type": "array", "description": "200 ok array", "items": {"properties": {"character_id": {"format": "int32", "title": "get_corporations_corporation_id_members_character_id", "type": "integer", "description": "character_id integer"}}, "title": "get_corporations_corporation_id_members_200_ok", "type": "object", "description": "200 ok object", "required": ["character_id"]}}, "description": "List of member character IDs", "headers": {"Last-Modified": {"type": "string", "description": "RFC7231 formatted datetime string"}, "Expires": {"type": "string", "description": "RFC7231 formatted datetime string"}, "Cache-Control": {"type": "string", "description": "The caching mechanism used"}}}, "403": {"examples": {"application/json": {"error": "Forbidden message"}}, "schema": {"$ref": "#/definitions/forbidden"}, "description": "Forbidden"}}, "description": "Read the current list of members if the calling character is a member.\n\n---\n\nThis route is cached for up to 3600 seconds", "security": [{"evesso": ["esi-corporations.read_corporation_membership.v1"]}], "x-cached-seconds": 3600, "operationId": "get_corporations_corporation_id_members", "parameters": [{"$ref": "#/parameters/corporation_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/token"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}]}}, "/v4/characters/{character_id}/": {"get": {"x-alternate-versions": ["dev", "v4"], "summary": "Get character's public information", "tags": ["Character"], "responses": {"404": {"examples": {"application/json": {"error": "Not found message"}}, "schema": {"properties": {"error": {"title": "get_characters_character_id_404_not_found", "type": "string", "description": "Not found message"}}, "title": "get_characters_character_id_not_found", "type": "object", "description": "Not found"}, "description": "Character not found"}, "500": {"examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error"}, "200": {"examples": {"application/json": {"bloodline_id": 3, "gender": "male", "name": "CCP Bartender", "description": "", "race_id": 2, "ancestry_id": 19, "corporation_id": 109299958, "birthday": "2015-03-24T11:37:00Z"}}, "schema": {"properties": {"security_status": {"maximum": 10, "description": "security_status number", "minimum": -10, "format": "float", "title": "get_characters_character_id_security_status", "type": "number"}, "bloodline_id": {"format": "int32", "title": "get_characters_character_id_bloodline_id", "type": "integer", "description": "bloodline_id integer"}, "gender": {"enum": ["female", "male"], "title": "get_characters_character_id_gender", "type": "string", "description": "gender string"}, "name": {"title": "get_characters_character_id_name", "type": "string", "description": "name string"}, "description": {"title": "get_characters_character_id_description", "type": "string", "description": "description string"}, "race_id": {"format": "int32", "title": "get_characters_character_id_race_id", "type": "integer", "description": "race_id integer"}, "ancestry_id": {"format": "int32", "title": "get_characters_character_id_ancestry_id", "type": "integer", "description": "ancestry_id integer"}, "corporation_id": {"format": "int32", "title": "get_characters_character_id_corporation_id", "type": "integer", "description": "The character's corporation ID"}, "birthday": {"format": "date-time", "title": "get_characters_character_id_birthday", "type": "string", "description": "Creation date of the character"}, "alliance_id": {"format": "int32", "title": "get_characters_character_id_alliance_id", "type": "integer", "description": "The character's alliance ID"}}, "title": "get_characters_character_id_ok", "type": "object", "description": "200 ok object", "required": ["corporation_id", "birthday", "name", "gender", "race_id", "bloodline_id"]}, "description": "Public data for the given character", "headers": {"Last-Modified": {"type": "string", "description": "RFC7231 formatted datetime string"}, "Expires": {"type": "string", "description": "RFC7231 formatted datetime string"}, "Cache-Control": {"type": "string", "description": "The caching mechanism used"}}}}, "x-cached-seconds": 3600, "description": "Public information about a character\n\n---\n\nThis route is cached for up to 3600 seconds", "operationId": "get_characters_character_id", "parameters": [{"$ref": "#/parameters/character_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}]}}, "/v1/characters/names/": {"get": {"x-alternate-versions": ["dev", "legacy", "v1"], "summary": "Get character names", "tags": ["Character"], "responses": {"500": {"examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error"}, "200": {"examples": {"application/json": [{"character_id": 95465499, "character_name": "CCP Bartender"}]}, "schema": {"title": "get_characters_names_ok", "maxItems": 1000, "type": "array", "description": "200 ok array", "items": {"properties": {"character_id": {"format": "int64", "title": "get_characters_names_character_id", "type": "integer", "description": "character_id integer"}, "character_name": {"title": "get_characters_names_character_name", "type": "string", "description": "character_name string"}}, "title": "get_characters_names_200_ok", "type": "object", "description": "200 ok object", "required": ["character_id", "character_name"]}}, "description": "List of id/name associations", "headers": {"Last-Modified": {"type": "string", "description": "RFC7231 formatted datetime string"}, "Expires": {"type": "string", "description": "RFC7231 formatted datetime string"}, "Cache-Control": {"type": "string", "description": "The caching mechanism used"}}}}, "x-cached-seconds": 3600, "description": "Resolve a set of character IDs to character names\n\n---\n\nThis route is cached for up to 3600 seconds", "operationId": "get_characters_names", "parameters": [{"maxItems": 1000, "name": "character_ids", "description": "A comma separated list of character IDs", "required": true, "in": "query", "minItems": 1, "type": "array", "items": {"format": "int64", "type": "integer"}}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}]}}}, "definitions": {"internal_server_error": {"properties": {"error": {"type": "string", "description": "Internal server error message"}}, "title": "Internal server error", "type": "object", "description": "Internal server error model", "required": ["error"]}, "forbidden": {"properties": {"error": {"type": "string", "description": "Forbidden message"}, "sso_status": {"type": "integer", "description": "Status code received from SSO"}}, "title": "Forbidden", "type": "object", "description": "Forbidden model", "required": ["error"]}}, "host": "esi.tech.ccp.is"} \ No newline at end of file +{"swagger": "2.0", "info": {"title": "EVE Swagger Interface", "description": "An OpenAPI for EVE Online", "version": "0.8.0"}, "host": "esi.tech.ccp.is", "schemes": ["https"], "produces": ["application/json"], "securityDefinitions": {"evesso": {"type": "oauth2", "authorizationUrl": "https://login.eveonline.com/oauth/authorize", "flow": "implicit", "scopes": {"esi-alliances.read_contacts.v1": "EVE SSO scope esi-alliances.read_contacts.v1", "esi-assets.read_assets.v1": "EVE SSO scope esi-assets.read_assets.v1", "esi-assets.read_corporation_assets.v1": "EVE SSO scope esi-assets.read_corporation_assets.v1", "esi-bookmarks.read_character_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_character_bookmarks.v1", "esi-bookmarks.read_corporation_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_corporation_bookmarks.v1", "esi-calendar.read_calendar_events.v1": "EVE SSO scope esi-calendar.read_calendar_events.v1", "esi-calendar.respond_calendar_events.v1": "EVE SSO scope esi-calendar.respond_calendar_events.v1", "esi-characters.read_agents_research.v1": "EVE SSO scope esi-characters.read_agents_research.v1", "esi-characters.read_blueprints.v1": "EVE SSO scope esi-characters.read_blueprints.v1", "esi-characters.read_chat_channels.v1": "EVE SSO scope esi-characters.read_chat_channels.v1", "esi-characters.read_contacts.v1": "EVE SSO scope esi-characters.read_contacts.v1", "esi-characters.read_corporation_roles.v1": "EVE SSO scope esi-characters.read_corporation_roles.v1", "esi-characters.read_fatigue.v1": "EVE SSO scope esi-characters.read_fatigue.v1", "esi-characters.read_fw_stats.v1": "EVE SSO scope esi-characters.read_fw_stats.v1", "esi-characters.read_loyalty.v1": "EVE SSO scope esi-characters.read_loyalty.v1", "esi-characters.read_medals.v1": "EVE SSO scope esi-characters.read_medals.v1", "esi-characters.read_notifications.v1": "EVE SSO scope esi-characters.read_notifications.v1", "esi-characters.read_opportunities.v1": "EVE SSO scope esi-characters.read_opportunities.v1", "esi-characters.read_standings.v1": "EVE SSO scope esi-characters.read_standings.v1", "esi-characters.read_titles.v1": "EVE SSO scope esi-characters.read_titles.v1", "esi-characters.write_contacts.v1": "EVE SSO scope esi-characters.write_contacts.v1", "esi-characterstats.read.v1": "EVE SSO scope esi-characterstats.read.v1", "esi-clones.read_clones.v1": "EVE SSO scope esi-clones.read_clones.v1", "esi-clones.read_implants.v1": "EVE SSO scope esi-clones.read_implants.v1", "esi-contracts.read_character_contracts.v1": "EVE SSO scope esi-contracts.read_character_contracts.v1", "esi-contracts.read_corporation_contracts.v1": "EVE SSO scope esi-contracts.read_corporation_contracts.v1", "esi-corporations.read_blueprints.v1": "EVE SSO scope esi-corporations.read_blueprints.v1", "esi-corporations.read_contacts.v1": "EVE SSO scope esi-corporations.read_contacts.v1", "esi-corporations.read_container_logs.v1": "EVE SSO scope esi-corporations.read_container_logs.v1", "esi-corporations.read_corporation_membership.v1": "EVE SSO scope esi-corporations.read_corporation_membership.v1", "esi-corporations.read_divisions.v1": "EVE SSO scope esi-corporations.read_divisions.v1", "esi-corporations.read_facilities.v1": "EVE SSO scope esi-corporations.read_facilities.v1", "esi-corporations.read_fw_stats.v1": "EVE SSO scope esi-corporations.read_fw_stats.v1", "esi-corporations.read_medals.v1": "EVE SSO scope esi-corporations.read_medals.v1", "esi-corporations.read_outposts.v1": "EVE SSO scope esi-corporations.read_outposts.v1", "esi-corporations.read_standings.v1": "EVE SSO scope esi-corporations.read_standings.v1", "esi-corporations.read_starbases.v1": "EVE SSO scope esi-corporations.read_starbases.v1", "esi-corporations.read_structures.v1": "EVE SSO scope esi-corporations.read_structures.v1", "esi-corporations.read_titles.v1": "EVE SSO scope esi-corporations.read_titles.v1", "esi-corporations.track_members.v1": "EVE SSO scope esi-corporations.track_members.v1", "esi-fittings.read_fittings.v1": "EVE SSO scope esi-fittings.read_fittings.v1", "esi-fittings.write_fittings.v1": "EVE SSO scope esi-fittings.write_fittings.v1", "esi-fleets.read_fleet.v1": "EVE SSO scope esi-fleets.read_fleet.v1", "esi-fleets.write_fleet.v1": "EVE SSO scope esi-fleets.write_fleet.v1", "esi-industry.read_character_jobs.v1": "EVE SSO scope esi-industry.read_character_jobs.v1", "esi-industry.read_character_mining.v1": "EVE SSO scope esi-industry.read_character_mining.v1", "esi-industry.read_corporation_jobs.v1": "EVE SSO scope esi-industry.read_corporation_jobs.v1", "esi-industry.read_corporation_mining.v1": "EVE SSO scope esi-industry.read_corporation_mining.v1", "esi-killmails.read_corporation_killmails.v1": "EVE SSO scope esi-killmails.read_corporation_killmails.v1", "esi-killmails.read_killmails.v1": "EVE SSO scope esi-killmails.read_killmails.v1", "esi-location.read_location.v1": "EVE SSO scope esi-location.read_location.v1", "esi-location.read_online.v1": "EVE SSO scope esi-location.read_online.v1", "esi-location.read_ship_type.v1": "EVE SSO scope esi-location.read_ship_type.v1", "esi-mail.organize_mail.v1": "EVE SSO scope esi-mail.organize_mail.v1", "esi-mail.read_mail.v1": "EVE SSO scope esi-mail.read_mail.v1", "esi-mail.send_mail.v1": "EVE SSO scope esi-mail.send_mail.v1", "esi-markets.read_character_orders.v1": "EVE SSO scope esi-markets.read_character_orders.v1", "esi-markets.read_corporation_orders.v1": "EVE SSO scope esi-markets.read_corporation_orders.v1", "esi-markets.structure_markets.v1": "EVE SSO scope esi-markets.structure_markets.v1", "esi-planets.manage_planets.v1": "EVE SSO scope esi-planets.manage_planets.v1", "esi-planets.read_customs_offices.v1": "EVE SSO scope esi-planets.read_customs_offices.v1", "esi-search.search_structures.v1": "EVE SSO scope esi-search.search_structures.v1", "esi-skills.read_skillqueue.v1": "EVE SSO scope esi-skills.read_skillqueue.v1", "esi-skills.read_skills.v1": "EVE SSO scope esi-skills.read_skills.v1", "esi-ui.open_window.v1": "EVE SSO scope esi-ui.open_window.v1", "esi-ui.write_waypoint.v1": "EVE SSO scope esi-ui.write_waypoint.v1", "esi-universe.read_structures.v1": "EVE SSO scope esi-universe.read_structures.v1", "esi-wallet.read_character_wallet.v1": "EVE SSO scope esi-wallet.read_character_wallet.v1", "esi-wallet.read_corporation_wallets.v1": "EVE SSO scope esi-wallet.read_corporation_wallets.v1"}}}, "parameters": {"datasource": {"name": "datasource", "description": "The server name you would like data from", "in": "query", "type": "string", "default": "tranquility", "enum": ["tranquility", "singularity"]}, "user_agent": {"name": "user_agent", "description": "Client identifier, takes precedence over headers", "in": "query", "type": "string"}, "X-User-Agent": {"name": "X-User-Agent", "description": "Client identifier, takes precedence over User-Agent", "in": "header", "type": "string"}, "page": {"name": "page", "description": "Which page of results to return", "in": "query", "type": "integer", "format": "int32", "default": 1}, "token": {"name": "token", "description": "Access token to use if unable to set a header", "in": "query", "type": "string"}, "character_id": {"description": "An EVE character ID", "format": "int32", "in": "path", "minimum": 1, "name": "character_id", "required": true, "type": "integer"}, "corporation_id": {"description": "An EVE corporation ID", "format": "int32", "in": "path", "minimum": 1, "name": "corporation_id", "required": true, "type": "integer"}, "language": {"name": "language", "description": "Language to use in the response", "in": "query", "type": "string", "default": "en-us", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"]}, "alliance_id": {"description": "An EVE alliance ID", "format": "int32", "in": "path", "minimum": 1, "name": "alliance_id", "required": true, "type": "integer"}}, "definitions": {"unauthorized": {"type": "object", "description": "Unauthorized model", "title": "Unauthorized", "required": ["error"], "properties": {"error": {"type": "string", "description": "Unauthorized message"}}, "x-model": "Unauthorized"}, "forbidden": {"type": "object", "description": "Forbidden model", "title": "Forbidden", "required": ["error"], "properties": {"error": {"type": "string", "description": "Forbidden message"}, "sso_status": {"type": "integer", "description": "Status code received from SSO"}}, "x-model": "Forbidden"}, "bad_request": {"type": "object", "description": "Bad request model", "title": "Bad request", "required": ["error"], "properties": {"error": {"type": "string", "description": "Bad request message"}}, "x-model": "Bad request"}, "internal_server_error": {"type": "object", "description": "Internal server error model", "title": "Internal server error", "required": ["error"], "properties": {"error": {"type": "string", "description": "Internal server error message"}}, "x-model": "Internal server error"}, "bad_gateway": {"type": "object", "description": "Bad gateway model", "title": "Bad gateway", "required": ["error"], "properties": {"error": {"type": "string", "description": "Bad gateway message"}}, "x-model": "Bad gateway"}, "service_unavailable": {"type": "object", "description": "Service unavailable model", "title": "Service unavailable", "required": ["error"], "properties": {"error": {"type": "string", "description": "Service unavailable message"}}, "x-model": "Service unavailable"}}, "paths": {"/v4/characters/{character_id}/": {"get": {"description": "Public information about a character\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get character's public information", "tags": ["Character"], "parameters": [{"$ref": "#/parameters/character_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Public data for the given character", "examples": {"application/json": {"corporation_id": 109299958, "birthday": "2015-03-24T11:37:00Z", "name": "CCP Bartender", "gender": "male", "race_id": 2, "description": "", "bloodline_id": 3, "ancestry_id": 19}}, "schema": {"type": "object", "required": ["corporation_id", "birthday", "name", "gender", "race_id", "bloodline_id"], "properties": {"name": {"type": "string", "title": "get_characters_character_id_name", "description": "name string"}, "description": {"type": "string", "title": "get_characters_character_id_description", "description": "description string"}, "corporation_id": {"type": "integer", "format": "int32", "description": "The character's corporation ID", "title": "get_characters_character_id_corporation_id"}, "alliance_id": {"type": "integer", "format": "int32", "description": "The character's alliance ID", "title": "get_characters_character_id_alliance_id"}, "birthday": {"type": "string", "format": "date-time", "description": "Creation date of the character", "title": "get_characters_character_id_birthday"}, "gender": {"type": "string", "enum": ["female", "male"], "title": "get_characters_character_id_gender", "description": "gender string"}, "race_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_race_id", "description": "race_id integer"}, "bloodline_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_bloodline_id", "description": "bloodline_id integer"}, "ancestry_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_ancestry_id", "description": "ancestry_id integer"}, "security_status": {"type": "number", "format": "float", "minimum": -10, "maximum": 10, "title": "get_characters_character_id_security_status", "description": "security_status number"}, "faction_id": {"type": "integer", "format": "int32", "description": "ID of the faction the character is fighting for, if the character is enlisted in Factional Warfare", "title": "get_characters_character_id_faction_id"}}, "title": "get_characters_character_id_ok", "description": "200 ok object", "x-model": "get_characters_character_id_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Character not found", "schema": {"type": "object", "title": "get_characters_character_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_characters_character_id_404_not_found"}}, "x-model": "get_characters_character_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_characters_character_id", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "v4"]}}, "/v1/characters/names/": {"get": {"description": "Resolve a set of character IDs to character names\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get character names", "tags": ["Character"], "parameters": [{"name": "character_ids", "in": "query", "description": "A comma separated list of character IDs", "required": true, "type": "array", "maxItems": 1000, "minItems": 1, "items": {"type": "integer", "format": "int64"}}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "List of id/name associations", "examples": {"application/json": [{"character_id": 95465499, "character_name": "CCP Bartender"}]}, "schema": {"type": "array", "maxItems": 1000, "items": {"type": "object", "required": ["character_id", "character_name"], "properties": {"character_id": {"type": "integer", "format": "int64", "title": "get_characters_names_character_id", "description": "character_id integer"}, "character_name": {"type": "string", "title": "get_characters_names_character_name", "description": "character_name string"}}, "title": "get_characters_names_200_ok", "description": "200 ok object", "x-model": "get_characters_names_200_ok"}, "title": "get_characters_names_ok", "description": "200 ok array"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_characters_names", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "legacy", "v1"]}}, "/v3/corporations/{corporation_id}/members/": {"get": {"description": "Return the current member list of a corporation, the token's character need to be a member of the corporation.\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get corporation members", "tags": ["Corporation"], "parameters": [{"$ref": "#/parameters/corporation_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/token", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "List of member character IDs", "examples": {"application/json": [90000001, 90000002]}, "schema": {"type": "array", "maxItems": 12601, "description": "A list of character IDs", "items": {"type": "integer", "format": "int32", "title": "get_corporations_corporation_id_members_200_ok", "description": "200 ok integer"}, "title": "get_corporations_corporation_id_members_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "403": {"description": "Forbidden", "schema": {"$ref": "#/definitions/forbidden", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Forbidden message"}}}, "401": {"description": "Unauthorized", "schema": {"$ref": "#/definitions/unauthorized", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Unauthorized message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "security": [{"evesso": ["esi-corporations.read_corporation_membership.v1"]}], "operationId": "get_corporations_corporation_id_members", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "v3"]}}}} \ No newline at end of file diff --git a/allianceauth/corputils/tests.py b/allianceauth/corputils/tests.py index 2c1bdfa4..3fa5617c 100644 --- a/allianceauth/corputils/tests.py +++ b/allianceauth/corputils/tests.py @@ -85,7 +85,7 @@ class CorpStatsUpdateTestCase(TestCase): @mock.patch('esi.clients.SwaggerClient') def test_update_add_member(self, SwaggerClient): SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2} - SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [{'character_id': 1}] + SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1] SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}] self.corpstats.update() self.assertTrue(CorpMember.objects.filter(character_id='1', character_name='test character', corpstats=self.corpstats).exists()) @@ -94,7 +94,7 @@ class CorpStatsUpdateTestCase(TestCase): def test_update_remove_member(self, SwaggerClient): CorpMember.objects.create(character_id='2', character_name='old test character', corpstats=self.corpstats) SwaggerClient.from_spec.return_value.Character.get_characters_character_id.return_value.result.return_value = {'corporation_id': 2} - SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [{'character_id': 1}] + SwaggerClient.from_spec.return_value.Corporation.get_corporations_corporation_id_members.return_value.result.return_value = [1] SwaggerClient.from_spec.return_value.Character.get_characters_names.return_value.result.return_value = [{'character_id': 1, 'character_name': 'test character'}] self.corpstats.update() self.assertFalse(CorpMember.objects.filter(character_id='2', corpstats=self.corpstats).exists()) diff --git a/allianceauth/eveonline/providers.py b/allianceauth/eveonline/providers.py index da641247..ae4472ec 100644 --- a/allianceauth/eveonline/providers.py +++ b/allianceauth/eveonline/providers.py @@ -81,7 +81,9 @@ class Alliance(Entity): @property def executor_corp(self): - return self.corp(self.executor_corp_id) + if self.executor_corp_id: + return self.corp(self.executor_corp_id) + return Entity(None, None) class Character(Entity): @@ -150,10 +152,10 @@ class EveSwaggerProvider(EveProvider): corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result() model = Alliance( id=alliance_id, - name=data['alliance_name'], + name=data['name'], ticker=data['ticker'], corp_ids=corps, - executor_corp_id=data['executor_corp'], + executor_corp_id=data['executor_corporation_id'] if 'executor_corporation_id' in data else None, ) return model except HTTPNotFound: @@ -164,7 +166,7 @@ class EveSwaggerProvider(EveProvider): data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result() model = Corporation( id=corp_id, - name=data['corporation_name'], + name=data['name'], ticker=data['ticker'], ceo_id=data['ceo_id'], members=data['member_count'], @@ -177,12 +179,11 @@ class EveSwaggerProvider(EveProvider): def get_character(self, character_id): try: data = self.client.Character.get_characters_character_id(character_id=character_id).result() - alliance_id = self.adapter.get_corp(data['corporation_id']).alliance_id model = Character( id=character_id, name=data['name'], corp_id=data['corporation_id'], - alliance_id=alliance_id, + alliance_id=data['alliance_id'] if 'alliance_id' in data else None, ) return model except (HTTPNotFound, HTTPUnprocessableEntity): diff --git a/allianceauth/eveonline/swagger.json b/allianceauth/eveonline/swagger.json index 1d06d376..b2696b41 100644 --- a/allianceauth/eveonline/swagger.json +++ b/allianceauth/eveonline/swagger.json @@ -1 +1 @@ -{"basePath": "/", "definitions": {"internal_server_error": {"description": "Internal server error model", "type": "object", "properties": {"error": {"description": "Internal server error message", "type": "string"}}, "required": ["error"], "title": "Internal server error"}, "forbidden": {"description": "Forbidden model", "type": "object", "properties": {"sso_status": {"description": "Status code received from SSO", "type": "integer"}, "error": {"description": "Forbidden message", "type": "string"}}, "required": ["error"], "title": "Forbidden"}}, "info": {"description": "An OpenAPI for EVE Online", "version": "0.5.5", "title": "EVE Swagger Interface"}, "parameters": {"character_id": {"description": "An EVE character ID", "name": "character_id", "in": "path", "type": "integer", "required": true, "format": "int32"}, "language": {"description": "Language to use in the response", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"], "default": "en-us", "in": "query", "type": "string", "name": "language"}, "X-User-Agent": {"description": "Client identifier, takes precedence over User-Agent", "type": "string", "name": "X-User-Agent", "in": "header"}, "datasource": {"description": "The server name you would like data from", "enum": ["tranquility", "singularity"], "default": "tranquility", "in": "query", "type": "string", "name": "datasource"}, "token": {"description": "Access token to use if unable to set a header", "type": "string", "name": "token", "in": "query"}, "corporation_id": {"description": "An EVE corporation ID", "name": "corporation_id", "in": "path", "type": "integer", "required": true, "format": "int32"}, "page": {"description": "Which page of results to return", "default": 1, "in": "query", "type": "integer", "name": "page", "format": "int32"}, "alliance_id": {"description": "An EVE alliance ID", "name": "alliance_id", "in": "path", "type": "integer", "required": true, "format": "int32"}, "user_agent": {"description": "Client identifier, takes precedence over headers", "type": "string", "name": "user_agent", "in": "query"}}, "produces": ["application/json"], "securityDefinitions": {"evesso": {"flow": "implicit", "scopes": {"esi-fleets.write_fleet.v1": "EVE SSO scope esi-fleets.write_fleet.v1", "esi-characters.read_chat_channels.v1": "EVE SSO scope esi-characters.read_chat_channels.v1", "esi-assets.read_corporation_assets.v1": "EVE SSO scope esi-assets.read_corporation_assets.v1", "esi-bookmarks.read_character_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_character_bookmarks.v1", "esi-killmails.read_killmails.v1": "EVE SSO scope esi-killmails.read_killmails.v1", "esi-characters.read_standings.v1": "EVE SSO scope esi-characters.read_standings.v1", "esi-wallet.read_character_wallet.v1": "EVE SSO scope esi-wallet.read_character_wallet.v1", "esi-corporations.write_structures.v1": "EVE SSO scope esi-corporations.write_structures.v1", "esi-markets.structure_markets.v1": "EVE SSO scope esi-markets.structure_markets.v1", "esi-universe.read_structures.v1": "EVE SSO scope esi-universe.read_structures.v1", "esi-location.read_online.v1": "EVE SSO scope esi-location.read_online.v1", "esi-fittings.write_fittings.v1": "EVE SSO scope esi-fittings.write_fittings.v1", "esi-industry.read_character_jobs.v1": "EVE SSO scope esi-industry.read_character_jobs.v1", "esi-corporations.track_members.v1": "EVE SSO scope esi-corporations.track_members.v1", "esi-characters.read_contacts.v1": "EVE SSO scope esi-characters.read_contacts.v1", "esi-search.search_structures.v1": "EVE SSO scope esi-search.search_structures.v1", "esi-clones.read_clones.v1": "EVE SSO scope esi-clones.read_clones.v1", "esi-skills.read_skills.v1": "EVE SSO scope esi-skills.read_skills.v1", "esi-characters.read_notifications.v1": "EVE SSO scope esi-characters.read_notifications.v1", "esi-location.read_location.v1": "EVE SSO scope esi-location.read_location.v1", "esi-characters.read_opportunities.v1": "EVE SSO scope esi-characters.read_opportunities.v1", "esi-characters.read_medals.v1": "EVE SSO scope esi-characters.read_medals.v1", "esi-corporations.read_contacts.v1": "EVE SSO scope esi-corporations.read_contacts.v1", "esi-ui.open_window.v1": "EVE SSO scope esi-ui.open_window.v1", "esi-characters.read_agents_research.v1": "EVE SSO scope esi-characters.read_agents_research.v1", "esi-location.read_ship_type.v1": "EVE SSO scope esi-location.read_ship_type.v1", "esi-calendar.read_calendar_events.v1": "EVE SSO scope esi-calendar.read_calendar_events.v1", "esi-mail.read_mail.v1": "EVE SSO scope esi-mail.read_mail.v1", "esi-characters.read_corporation_roles.v1": "EVE SSO scope esi-characters.read_corporation_roles.v1", "esi-characters.read_blueprints.v1": "EVE SSO scope esi-characters.read_blueprints.v1", "esi-ui.write_waypoint.v1": "EVE SSO scope esi-ui.write_waypoint.v1", "esi-fleets.read_fleet.v1": "EVE SSO scope esi-fleets.read_fleet.v1", "esi-markets.read_character_orders.v1": "EVE SSO scope esi-markets.read_character_orders.v1", "esi-mail.send_mail.v1": "EVE SSO scope esi-mail.send_mail.v1", "esi-corporations.read_divisions.v1": "EVE SSO scope esi-corporations.read_divisions.v1", "esi-skills.read_skillqueue.v1": "EVE SSO scope esi-skills.read_skillqueue.v1", "esi-mail.organize_mail.v1": "EVE SSO scope esi-mail.organize_mail.v1", "esi-characters.write_contacts.v1": "EVE SSO scope esi-characters.write_contacts.v1", "esi-calendar.respond_calendar_events.v1": "EVE SSO scope esi-calendar.respond_calendar_events.v1", "esi-corporations.read_structures.v1": "EVE SSO scope esi-corporations.read_structures.v1", "esi-assets.read_assets.v1": "EVE SSO scope esi-assets.read_assets.v1", "esi-planets.manage_planets.v1": "EVE SSO scope esi-planets.manage_planets.v1", "esi-wallet.read_corporation_wallets.v1": "EVE SSO scope esi-wallet.read_corporation_wallets.v1", "esi-characters.read_loyalty.v1": "EVE SSO scope esi-characters.read_loyalty.v1", "esi-killmails.read_corporation_killmails.v1": "EVE SSO scope esi-killmails.read_corporation_killmails.v1", "esi-corporations.read_corporation_membership.v1": "EVE SSO scope esi-corporations.read_corporation_membership.v1", "esi-clones.read_implants.v1": "EVE SSO scope esi-clones.read_implants.v1", "esi-contracts.read_character_contracts.v1": "EVE SSO scope esi-contracts.read_character_contracts.v1", "esi-fittings.read_fittings.v1": "EVE SSO scope esi-fittings.read_fittings.v1", "esi-characters.read_fatigue.v1": "EVE SSO scope esi-characters.read_fatigue.v1"}, "authorizationUrl": "https://login.eveonline.com/oauth/authorize", "type": "oauth2"}}, "schemes": ["https"], "swagger": "2.0", "host": "esi.tech.ccp.is", "paths": {"/v1/alliances/{alliance_id}/corporations/": {"get": {"description": "List all current member corporations of an alliance\n\n---\n\nThis route is cached for up to 3600 seconds", "x-cached-seconds": 3600, "operationId": "get_alliances_alliance_id_corporations", "summary": "List alliance's corporations", "responses": {"200": {"schema": {"description": "200 ok array", "type": "array", "title": "get_alliances_alliance_id_corporations_ok", "maxItems": 1000, "items": {"minimum": 0, "uniqueItems": true, "description": "200 ok integer", "type": "integer", "title": "get_alliances_alliance_id_corporations_200_ok", "format": "int32"}}, "description": "List of corporation IDs", "headers": {"Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}}, "examples": {"application/json": [98000001]}}, "500": {"schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}}}, "tags": ["Alliance"], "parameters": [{"$ref": "#/parameters/alliance_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-alternate-versions": ["dev", "legacy", "v1"]}}, "/v4/characters/{character_id}/": {"get": {"description": "Public information about a character\n\n---\n\nThis route is cached for up to 3600 seconds", "x-cached-seconds": 3600, "operationId": "get_characters_character_id", "summary": "Get character's public information", "responses": {"200": {"schema": {"description": "200 ok object", "type": "object", "properties": {"bloodline_id": {"description": "bloodline_id integer", "type": "integer", "title": "get_characters_character_id_bloodline_id", "format": "int32"}, "name": {"description": "name string", "type": "string", "title": "get_characters_character_id_name"}, "race_id": {"description": "race_id integer", "type": "integer", "title": "get_characters_character_id_race_id", "format": "int32"}, "security_status": {"minimum": -10, "maximum": 10, "description": "security_status number", "type": "number", "title": "get_characters_character_id_security_status", "format": "float"}, "description": {"description": "description string", "type": "string", "title": "get_characters_character_id_description"}, "corporation_id": {"description": "The character's corporation ID", "type": "integer", "title": "get_characters_character_id_corporation_id", "format": "int32"}, "birthday": {"description": "Creation date of the character", "type": "string", "title": "get_characters_character_id_birthday", "format": "date-time"}, "alliance_id": {"description": "The character's alliance ID", "type": "integer", "title": "get_characters_character_id_alliance_id", "format": "int32"}, "gender": {"description": "gender string", "enum": ["female", "male"], "type": "string", "title": "get_characters_character_id_gender"}, "ancestry_id": {"description": "ancestry_id integer", "type": "integer", "title": "get_characters_character_id_ancestry_id", "format": "int32"}}, "required": ["corporation_id", "birthday", "name", "gender", "race_id", "bloodline_id"], "title": "get_characters_character_id_ok"}, "description": "Public data for the given character", "headers": {"Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}}, "examples": {"application/json": {"bloodline_id": 3, "name": "CCP Bartender", "race_id": 2, "description": "", "corporation_id": 109299958, "birthday": "2015-03-24T11:37:00Z", "gender": "male", "ancestry_id": 19}}}, "500": {"schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}}, "404": {"schema": {"description": "Not found", "type": "object", "properties": {"error": {"description": "Not found message", "type": "string", "title": "get_characters_character_id_404_not_found"}}, "title": "get_characters_character_id_not_found"}, "description": "Character not found", "examples": {"application/json": {"error": "Not found message"}}}}, "tags": ["Character"], "parameters": [{"$ref": "#/parameters/character_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-alternate-versions": ["dev", "v4"]}}, "/v2/universe/types/{type_id}/": {"get": {"description": "Get information on a type\n\n---\n\nThis route expires daily at 11:05", "operationId": "get_universe_types_type_id", "summary": "Get type information", "responses": {"200": {"schema": {"description": "200 ok object", "type": "object", "properties": {"published": {"description": "published boolean", "type": "boolean", "title": "get_universe_types_type_id_published"}, "group_id": {"description": "group_id integer", "type": "integer", "title": "get_universe_types_type_id_group_id", "format": "int32"}, "mass": {"description": "mass number", "type": "number", "title": "get_universe_types_type_id_mass", "format": "float"}, "graphic_id": {"description": "graphic_id integer", "type": "integer", "title": "get_universe_types_type_id_graphic_id", "format": "int32"}, "type_id": {"description": "type_id integer", "type": "integer", "title": "get_universe_types_type_id_type_id", "format": "int32"}, "volume": {"description": "volume number", "type": "number", "title": "get_universe_types_type_id_volume", "format": "float"}, "radius": {"description": "radius number", "type": "number", "title": "get_universe_types_type_id_radius", "format": "float"}, "dogma_effects": {"description": "dogma_effects array", "type": "array", "title": "get_universe_types_type_id_dogma_effects", "maxItems": 1000, "items": {"description": "dogma_effect object", "type": "object", "properties": {"effect_id": {"description": "effect_id integer", "type": "integer", "title": "get_universe_types_type_id_effect_id", "format": "int32"}, "is_default": {"description": "is_default boolean", "type": "boolean", "title": "get_universe_types_type_id_is_default"}}, "required": ["effect_id", "is_default"], "title": "get_universe_types_type_id_dogma_effect"}}, "portion_size": {"description": "portion_size integer", "type": "integer", "title": "get_universe_types_type_id_portion_size", "format": "int32"}, "description": {"description": "description string", "type": "string", "title": "get_universe_types_type_id_description"}, "name": {"description": "name string", "type": "string", "title": "get_universe_types_type_id_name"}, "icon_id": {"description": "icon_id integer", "type": "integer", "title": "get_universe_types_type_id_icon_id", "format": "int32"}, "capacity": {"description": "capacity number", "type": "number", "title": "get_universe_types_type_id_capacity", "format": "float"}, "dogma_attributes": {"description": "dogma_attributes array", "type": "array", "title": "get_universe_types_type_id_dogma_attributes", "maxItems": 1000, "items": {"description": "dogma_attribute object", "type": "object", "properties": {"attribute_id": {"description": "attribute_id integer", "type": "integer", "title": "get_universe_types_type_id_attribute_id", "format": "int32"}, "value": {"description": "value number", "type": "number", "title": "get_universe_types_type_id_value", "format": "float"}}, "required": ["attribute_id", "value"], "title": "get_universe_types_type_id_dogma_attribute"}}}, "required": ["type_id", "name", "description", "published", "group_id"], "title": "get_universe_types_type_id_ok"}, "description": "Information about a type", "headers": {"Content-Language": {"description": "The language used in the response", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"], "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}}, "examples": {"application/json": {"published": true, "description": "The Rifter is a...", "type_id": 587, "name": "Rifter", "group_id": 25}}}, "500": {"schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}}, "404": {"schema": {"description": "Not found", "type": "object", "properties": {"error": {"description": "Not found message", "type": "string", "title": "get_universe_types_type_id_404_not_found"}}, "title": "get_universe_types_type_id_not_found"}, "description": "Type not found", "examples": {"application/json": {"error": "Not found message"}}}}, "tags": ["Universe"], "parameters": [{"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/language"}, {"description": "An Eve item type ID", "name": "type_id", "in": "path", "type": "integer", "required": true, "format": "int32"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-alternate-versions": ["legacy", "v2"]}}, "/v2/alliances/{alliance_id}/": {"get": {"description": "Public information about an alliance\n\n---\n\nThis route is cached for up to 3600 seconds", "x-cached-seconds": 3600, "operationId": "get_alliances_alliance_id", "summary": "Get alliance information", "responses": {"200": {"schema": {"description": "200 ok object", "type": "object", "properties": {"alliance_name": {"description": "the full name of the alliance", "type": "string", "title": "get_alliances_alliance_id_alliance_name"}, "executor_corp": {"description": "the executor corporation ID, if this alliance is not closed", "type": "integer", "title": "get_alliances_alliance_id_executor_corp", "format": "int32"}, "ticker": {"description": "the short name of the alliance", "type": "string", "title": "get_alliances_alliance_id_ticker"}, "date_founded": {"description": "date_founded string", "type": "string", "title": "get_alliances_alliance_id_date_founded", "format": "date-time"}}, "required": ["alliance_name", "ticker", "date_founded"], "title": "get_alliances_alliance_id_ok"}, "description": "Public data about an alliance", "headers": {"Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}}, "examples": {"application/json": {"alliance_name": "C C P Alliance", "executor_corp": 98356193, "ticker": "", "date_founded": "2016-06-26T21:00:00Z"}}}, "500": {"schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}}, "404": {"schema": {"description": "Alliance not found", "type": "object", "properties": {"error": {"description": "error message", "type": "string", "title": "get_alliances_alliance_id_error"}}, "title": "get_alliances_alliance_id_not_found"}, "description": "Alliance not found", "examples": {"application/json": {"error": "Alliance not found"}}}}, "tags": ["Alliance"], "parameters": [{"$ref": "#/parameters/alliance_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-alternate-versions": ["v2"]}}, "/v3/corporations/{corporation_id}/": {"get": {"description": "Public information about a corporation\n\n---\n\nThis route is cached for up to 3600 seconds", "x-cached-seconds": 3600, "operationId": "get_corporations_corporation_id", "summary": "Get corporation information", "responses": {"200": {"schema": {"description": "200 ok object", "type": "object", "properties": {"creator_id": {"description": "creator_id integer", "type": "integer", "title": "get_corporations_corporation_id_creator_id", "format": "int32"}, "url": {"description": "url string", "type": "string", "title": "get_corporations_corporation_id_url"}, "ticker": {"description": "the short name of the corporation", "type": "string", "title": "get_corporations_corporation_id_ticker"}, "member_count": {"description": "member_count integer", "type": "integer", "title": "get_corporations_corporation_id_member_count", "format": "int32"}, "corporation_description": {"description": "corporation_description string", "type": "string", "title": "get_corporations_corporation_id_corporation_description"}, "creation_date": {"description": "creation_date string", "type": "string", "title": "get_corporations_corporation_id_creation_date", "format": "date-time"}, "faction": {"description": "faction string", "enum": ["Minmatar", "Gallente", "Caldari", "Amarr"], "type": "string", "title": "get_corporations_corporation_id_faction"}, "corporation_name": {"description": "the full name of the corporation", "type": "string", "title": "get_corporations_corporation_id_corporation_name"}, "alliance_id": {"description": "id of alliance that corporation is a member of, if any", "type": "integer", "title": "get_corporations_corporation_id_alliance_id", "format": "int32"}, "ceo_id": {"description": "ceo_id integer", "type": "integer", "title": "get_corporations_corporation_id_ceo_id", "format": "int32"}, "tax_rate": {"minimum": 0, "maximum": 1, "description": "tax_rate number", "type": "number", "title": "get_corporations_corporation_id_tax_rate", "format": "float"}}, "required": ["corporation_name", "ticker", "member_count", "ceo_id", "corporation_description", "tax_rate", "creator_id", "url"], "title": "get_corporations_corporation_id_ok"}, "description": "Public data about a corporation", "headers": {"Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}}, "examples": {"application/json": {"creator_id": 180548812, "url": "http://www.eveonline.com", "ticker": "-CCP-", "member_count": 656, "corporation_description": "This is a corporation description, it's basically just a string", "creation_date": "2004-11-28T16:42:51Z", "corporation_name": "C C P", "alliance_id": 434243723, "ceo_id": 180548812, "tax_rate": 0.256}}}, "500": {"schema": {"$ref": "#/definitions/internal_server_error"}, "description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}}, "404": {"schema": {"description": "Not found", "type": "object", "properties": {"error": {"description": "Not found message", "type": "string", "title": "get_corporations_corporation_id_404_not_found"}}, "title": "get_corporations_corporation_id_not_found"}, "description": "Corporation not found", "examples": {"application/json": {"error": "Not found message"}}}}, "tags": ["Corporation"], "parameters": [{"$ref": "#/parameters/corporation_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-alternate-versions": ["v3"]}}}} \ No newline at end of file +{"swagger": "2.0", "info": {"title": "EVE Swagger Interface", "description": "An OpenAPI for EVE Online", "version": "0.8.0"}, "host": "esi.tech.ccp.is", "schemes": ["https"], "produces": ["application/json"], "securityDefinitions": {"evesso": {"type": "oauth2", "authorizationUrl": "https://login.eveonline.com/oauth/authorize", "flow": "implicit", "scopes": {"esi-alliances.read_contacts.v1": "EVE SSO scope esi-alliances.read_contacts.v1", "esi-assets.read_assets.v1": "EVE SSO scope esi-assets.read_assets.v1", "esi-assets.read_corporation_assets.v1": "EVE SSO scope esi-assets.read_corporation_assets.v1", "esi-bookmarks.read_character_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_character_bookmarks.v1", "esi-bookmarks.read_corporation_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_corporation_bookmarks.v1", "esi-calendar.read_calendar_events.v1": "EVE SSO scope esi-calendar.read_calendar_events.v1", "esi-calendar.respond_calendar_events.v1": "EVE SSO scope esi-calendar.respond_calendar_events.v1", "esi-characters.read_agents_research.v1": "EVE SSO scope esi-characters.read_agents_research.v1", "esi-characters.read_blueprints.v1": "EVE SSO scope esi-characters.read_blueprints.v1", "esi-characters.read_chat_channels.v1": "EVE SSO scope esi-characters.read_chat_channels.v1", "esi-characters.read_contacts.v1": "EVE SSO scope esi-characters.read_contacts.v1", "esi-characters.read_corporation_roles.v1": "EVE SSO scope esi-characters.read_corporation_roles.v1", "esi-characters.read_fatigue.v1": "EVE SSO scope esi-characters.read_fatigue.v1", "esi-characters.read_fw_stats.v1": "EVE SSO scope esi-characters.read_fw_stats.v1", "esi-characters.read_loyalty.v1": "EVE SSO scope esi-characters.read_loyalty.v1", "esi-characters.read_medals.v1": "EVE SSO scope esi-characters.read_medals.v1", "esi-characters.read_notifications.v1": "EVE SSO scope esi-characters.read_notifications.v1", "esi-characters.read_opportunities.v1": "EVE SSO scope esi-characters.read_opportunities.v1", "esi-characters.read_standings.v1": "EVE SSO scope esi-characters.read_standings.v1", "esi-characters.read_titles.v1": "EVE SSO scope esi-characters.read_titles.v1", "esi-characters.write_contacts.v1": "EVE SSO scope esi-characters.write_contacts.v1", "esi-characterstats.read.v1": "EVE SSO scope esi-characterstats.read.v1", "esi-clones.read_clones.v1": "EVE SSO scope esi-clones.read_clones.v1", "esi-clones.read_implants.v1": "EVE SSO scope esi-clones.read_implants.v1", "esi-contracts.read_character_contracts.v1": "EVE SSO scope esi-contracts.read_character_contracts.v1", "esi-contracts.read_corporation_contracts.v1": "EVE SSO scope esi-contracts.read_corporation_contracts.v1", "esi-corporations.read_blueprints.v1": "EVE SSO scope esi-corporations.read_blueprints.v1", "esi-corporations.read_contacts.v1": "EVE SSO scope esi-corporations.read_contacts.v1", "esi-corporations.read_container_logs.v1": "EVE SSO scope esi-corporations.read_container_logs.v1", "esi-corporations.read_corporation_membership.v1": "EVE SSO scope esi-corporations.read_corporation_membership.v1", "esi-corporations.read_divisions.v1": "EVE SSO scope esi-corporations.read_divisions.v1", "esi-corporations.read_facilities.v1": "EVE SSO scope esi-corporations.read_facilities.v1", "esi-corporations.read_fw_stats.v1": "EVE SSO scope esi-corporations.read_fw_stats.v1", "esi-corporations.read_medals.v1": "EVE SSO scope esi-corporations.read_medals.v1", "esi-corporations.read_outposts.v1": "EVE SSO scope esi-corporations.read_outposts.v1", "esi-corporations.read_standings.v1": "EVE SSO scope esi-corporations.read_standings.v1", "esi-corporations.read_starbases.v1": "EVE SSO scope esi-corporations.read_starbases.v1", "esi-corporations.read_structures.v1": "EVE SSO scope esi-corporations.read_structures.v1", "esi-corporations.read_titles.v1": "EVE SSO scope esi-corporations.read_titles.v1", "esi-corporations.track_members.v1": "EVE SSO scope esi-corporations.track_members.v1", "esi-fittings.read_fittings.v1": "EVE SSO scope esi-fittings.read_fittings.v1", "esi-fittings.write_fittings.v1": "EVE SSO scope esi-fittings.write_fittings.v1", "esi-fleets.read_fleet.v1": "EVE SSO scope esi-fleets.read_fleet.v1", "esi-fleets.write_fleet.v1": "EVE SSO scope esi-fleets.write_fleet.v1", "esi-industry.read_character_jobs.v1": "EVE SSO scope esi-industry.read_character_jobs.v1", "esi-industry.read_character_mining.v1": "EVE SSO scope esi-industry.read_character_mining.v1", "esi-industry.read_corporation_jobs.v1": "EVE SSO scope esi-industry.read_corporation_jobs.v1", "esi-industry.read_corporation_mining.v1": "EVE SSO scope esi-industry.read_corporation_mining.v1", "esi-killmails.read_corporation_killmails.v1": "EVE SSO scope esi-killmails.read_corporation_killmails.v1", "esi-killmails.read_killmails.v1": "EVE SSO scope esi-killmails.read_killmails.v1", "esi-location.read_location.v1": "EVE SSO scope esi-location.read_location.v1", "esi-location.read_online.v1": "EVE SSO scope esi-location.read_online.v1", "esi-location.read_ship_type.v1": "EVE SSO scope esi-location.read_ship_type.v1", "esi-mail.organize_mail.v1": "EVE SSO scope esi-mail.organize_mail.v1", "esi-mail.read_mail.v1": "EVE SSO scope esi-mail.read_mail.v1", "esi-mail.send_mail.v1": "EVE SSO scope esi-mail.send_mail.v1", "esi-markets.read_character_orders.v1": "EVE SSO scope esi-markets.read_character_orders.v1", "esi-markets.read_corporation_orders.v1": "EVE SSO scope esi-markets.read_corporation_orders.v1", "esi-markets.structure_markets.v1": "EVE SSO scope esi-markets.structure_markets.v1", "esi-planets.manage_planets.v1": "EVE SSO scope esi-planets.manage_planets.v1", "esi-planets.read_customs_offices.v1": "EVE SSO scope esi-planets.read_customs_offices.v1", "esi-search.search_structures.v1": "EVE SSO scope esi-search.search_structures.v1", "esi-skills.read_skillqueue.v1": "EVE SSO scope esi-skills.read_skillqueue.v1", "esi-skills.read_skills.v1": "EVE SSO scope esi-skills.read_skills.v1", "esi-ui.open_window.v1": "EVE SSO scope esi-ui.open_window.v1", "esi-ui.write_waypoint.v1": "EVE SSO scope esi-ui.write_waypoint.v1", "esi-universe.read_structures.v1": "EVE SSO scope esi-universe.read_structures.v1", "esi-wallet.read_character_wallet.v1": "EVE SSO scope esi-wallet.read_character_wallet.v1", "esi-wallet.read_corporation_wallets.v1": "EVE SSO scope esi-wallet.read_corporation_wallets.v1"}}}, "parameters": {"datasource": {"name": "datasource", "description": "The server name you would like data from", "in": "query", "type": "string", "default": "tranquility", "enum": ["tranquility", "singularity"]}, "user_agent": {"name": "user_agent", "description": "Client identifier, takes precedence over headers", "in": "query", "type": "string"}, "X-User-Agent": {"name": "X-User-Agent", "description": "Client identifier, takes precedence over User-Agent", "in": "header", "type": "string"}, "page": {"name": "page", "description": "Which page of results to return", "in": "query", "type": "integer", "format": "int32", "default": 1}, "token": {"name": "token", "description": "Access token to use if unable to set a header", "in": "query", "type": "string"}, "character_id": {"description": "An EVE character ID", "format": "int32", "in": "path", "minimum": 1, "name": "character_id", "required": true, "type": "integer"}, "corporation_id": {"description": "An EVE corporation ID", "format": "int32", "in": "path", "minimum": 1, "name": "corporation_id", "required": true, "type": "integer"}, "language": {"name": "language", "description": "Language to use in the response", "in": "query", "type": "string", "default": "en-us", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"]}, "alliance_id": {"description": "An EVE alliance ID", "format": "int32", "in": "path", "minimum": 1, "name": "alliance_id", "required": true, "type": "integer"}}, "definitions": {"unauthorized": {"type": "object", "description": "Unauthorized model", "title": "Unauthorized", "required": ["error"], "properties": {"error": {"type": "string", "description": "Unauthorized message"}}, "x-model": "Unauthorized"}, "forbidden": {"type": "object", "description": "Forbidden model", "title": "Forbidden", "required": ["error"], "properties": {"error": {"type": "string", "description": "Forbidden message"}, "sso_status": {"type": "integer", "description": "Status code received from SSO"}}, "x-model": "Forbidden"}, "bad_request": {"type": "object", "description": "Bad request model", "title": "Bad request", "required": ["error"], "properties": {"error": {"type": "string", "description": "Bad request message"}}, "x-model": "Bad request"}, "internal_server_error": {"type": "object", "description": "Internal server error model", "title": "Internal server error", "required": ["error"], "properties": {"error": {"type": "string", "description": "Internal server error message"}}, "x-model": "Internal server error"}, "bad_gateway": {"type": "object", "description": "Bad gateway model", "title": "Bad gateway", "required": ["error"], "properties": {"error": {"type": "string", "description": "Bad gateway message"}}, "x-model": "Bad gateway"}, "service_unavailable": {"type": "object", "description": "Service unavailable model", "title": "Service unavailable", "required": ["error"], "properties": {"error": {"type": "string", "description": "Service unavailable message"}}, "x-model": "Service unavailable"}}, "paths": {"/v3/alliances/{alliance_id}/": {"get": {"description": "Public information about an alliance\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get alliance information", "tags": ["Alliance"], "parameters": [{"$ref": "#/parameters/alliance_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Public data about an alliance", "examples": {"application/json": {"name": "C C P Alliance", "ticker": "", "creator_id": 12345, "creator_corporation_id": 45678, "executor_corporation_id": 98356193, "date_founded": "2016-06-26T21:00:00Z"}}, "schema": {"type": "object", "required": ["name", "creator_id", "creator_corporation_id", "ticker", "date_founded"], "properties": {"name": {"type": "string", "description": "the full name of the alliance", "title": "get_alliances_alliance_id_name"}, "creator_id": {"type": "integer", "format": "int32", "description": "ID of the character that created the alliance", "title": "get_alliances_alliance_id_creator_id"}, "creator_corporation_id": {"type": "integer", "format": "int32", "description": "ID of the corporation that created the alliance", "title": "get_alliances_alliance_id_creator_corporation_id"}, "ticker": {"type": "string", "description": "the short name of the alliance", "title": "get_alliances_alliance_id_ticker"}, "executor_corporation_id": {"type": "integer", "format": "int32", "description": "the executor corporation ID, if this alliance is not closed", "title": "get_alliances_alliance_id_executor_corporation_id"}, "date_founded": {"type": "string", "format": "date-time", "title": "get_alliances_alliance_id_date_founded", "description": "date_founded string"}, "faction_id": {"type": "integer", "format": "int32", "description": "Faction ID this alliance is fighting for, if this alliance is enlisted in factional warfare", "title": "get_alliances_alliance_id_faction_id"}}, "title": "get_alliances_alliance_id_ok", "description": "200 ok object", "x-model": "get_alliances_alliance_id_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Alliance not found", "schema": {"type": "object", "title": "get_alliances_alliance_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_alliances_alliance_id_404_not_found"}}, "x-model": "get_alliances_alliance_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_alliances_alliance_id", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "v3"]}}, "/v1/alliances/{alliance_id}/corporations/": {"get": {"description": "List all current member corporations of an alliance\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "List alliance's corporations", "tags": ["Alliance"], "parameters": [{"$ref": "#/parameters/alliance_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "List of corporation IDs", "examples": {"application/json": [98000001]}, "schema": {"type": "array", "maxItems": 1000, "items": {"type": "integer", "format": "int32", "minimum": 0, "uniqueItems": true, "title": "get_alliances_alliance_id_corporations_200_ok", "description": "200 ok integer"}, "title": "get_alliances_alliance_id_corporations_ok", "description": "200 ok array"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_alliances_alliance_id_corporations", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "legacy", "v1"]}}, "/v4/characters/{character_id}/": {"get": {"description": "Public information about a character\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get character's public information", "tags": ["Character"], "parameters": [{"$ref": "#/parameters/character_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Public data for the given character", "examples": {"application/json": {"corporation_id": 109299958, "birthday": "2015-03-24T11:37:00Z", "name": "CCP Bartender", "gender": "male", "race_id": 2, "description": "", "bloodline_id": 3, "ancestry_id": 19}}, "schema": {"type": "object", "required": ["corporation_id", "birthday", "name", "gender", "race_id", "bloodline_id"], "properties": {"name": {"type": "string", "title": "get_characters_character_id_name", "description": "name string"}, "description": {"type": "string", "title": "get_characters_character_id_description", "description": "description string"}, "corporation_id": {"type": "integer", "format": "int32", "description": "The character's corporation ID", "title": "get_characters_character_id_corporation_id"}, "alliance_id": {"type": "integer", "format": "int32", "description": "The character's alliance ID", "title": "get_characters_character_id_alliance_id"}, "birthday": {"type": "string", "format": "date-time", "description": "Creation date of the character", "title": "get_characters_character_id_birthday"}, "gender": {"type": "string", "enum": ["female", "male"], "title": "get_characters_character_id_gender", "description": "gender string"}, "race_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_race_id", "description": "race_id integer"}, "bloodline_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_bloodline_id", "description": "bloodline_id integer"}, "ancestry_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_ancestry_id", "description": "ancestry_id integer"}, "security_status": {"type": "number", "format": "float", "minimum": -10, "maximum": 10, "title": "get_characters_character_id_security_status", "description": "security_status number"}, "faction_id": {"type": "integer", "format": "int32", "description": "ID of the faction the character is fighting for, if the character is enlisted in Factional Warfare", "title": "get_characters_character_id_faction_id"}}, "title": "get_characters_character_id_ok", "description": "200 ok object", "x-model": "get_characters_character_id_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Character not found", "schema": {"type": "object", "title": "get_characters_character_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_characters_character_id_404_not_found"}}, "x-model": "get_characters_character_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_characters_character_id", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "v4"]}}, "/v4/corporations/{corporation_id}/": {"get": {"description": "Public information about a corporation\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get corporation information", "tags": ["Corporation"], "parameters": [{"$ref": "#/parameters/corporation_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Public information about a corporation", "examples": {"application/json": {"name": "C C P", "ticker": "-CCP-", "member_count": 656, "ceo_id": 180548812, "alliance_id": 434243723, "description": "This is a corporation description, it's basically just a string", "tax_rate": 0.256, "date_founded": "2004-11-28T16:42:51Z", "creator_id": 180548812, "url": "http://www.eveonline.com"}}, "schema": {"type": "object", "required": ["name", "ticker", "member_count", "ceo_id", "tax_rate", "creator_id"], "properties": {"name": {"type": "string", "description": "the full name of the corporation", "title": "get_corporations_corporation_id_name"}, "ticker": {"type": "string", "description": "the short name of the corporation", "title": "get_corporations_corporation_id_ticker"}, "member_count": {"type": "integer", "format": "int32", "title": "get_corporations_corporation_id_member_count", "description": "member_count integer"}, "ceo_id": {"type": "integer", "format": "int32", "title": "get_corporations_corporation_id_ceo_id", "description": "ceo_id integer"}, "alliance_id": {"type": "integer", "format": "int32", "description": "ID of the alliance that corporation is a member of, if any", "title": "get_corporations_corporation_id_alliance_id"}, "description": {"type": "string", "title": "get_corporations_corporation_id_description", "description": "description string"}, "tax_rate": {"type": "number", "format": "float", "minimum": 0, "maximum": 1, "title": "get_corporations_corporation_id_tax_rate", "description": "tax_rate number"}, "date_founded": {"type": "string", "format": "date-time", "title": "get_corporations_corporation_id_date_founded", "description": "date_founded string"}, "creator_id": {"type": "integer", "format": "int32", "title": "get_corporations_corporation_id_creator_id", "description": "creator_id integer"}, "url": {"type": "string", "title": "get_corporations_corporation_id_url", "description": "url string"}, "faction_id": {"type": "integer", "format": "int32", "title": "get_corporations_corporation_id_faction_id", "description": "faction_id integer"}, "home_station_id": {"type": "integer", "format": "int32", "title": "get_corporations_corporation_id_home_station_id", "description": "home_station_id integer"}, "shares": {"type": "integer", "format": "int64", "title": "get_corporations_corporation_id_shares", "description": "shares integer"}}, "title": "get_corporations_corporation_id_ok", "description": "200 ok object", "x-model": "get_corporations_corporation_id_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Corporation not found", "schema": {"type": "object", "title": "get_corporations_corporation_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_corporations_corporation_id_404_not_found"}}, "x-model": "get_corporations_corporation_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_corporations_corporation_id", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "v4"]}}, "/v3/universe/types/{type_id}/": {"get": {"description": "Get information on a type\n\n---\n\nThis route expires daily at 11:05", "summary": "Get type information", "tags": ["Universe"], "parameters": [{"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/language", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"name": "type_id", "in": "path", "description": "An Eve item type ID", "required": true, "type": "integer", "format": "int32"}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Information about a type", "examples": {"application/json": {"type_id": 587, "name": "Rifter", "description": "The Rifter is a...", "published": true, "group_id": 25}}, "schema": {"type": "object", "required": ["type_id", "name", "description", "published", "group_id"], "properties": {"type_id": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_type_id", "description": "type_id integer"}, "name": {"type": "string", "title": "get_universe_types_type_id_name", "description": "name string"}, "description": {"type": "string", "title": "get_universe_types_type_id_description", "description": "description string"}, "published": {"type": "boolean", "title": "get_universe_types_type_id_published", "description": "published boolean"}, "group_id": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_group_id", "description": "group_id integer"}, "market_group_id": {"type": "integer", "format": "int32", "description": "This only exists for types that can be put on the market", "title": "get_universe_types_type_id_market_group_id"}, "radius": {"type": "number", "format": "float", "title": "get_universe_types_type_id_radius", "description": "radius number"}, "volume": {"type": "number", "format": "float", "title": "get_universe_types_type_id_volume", "description": "volume number"}, "packaged_volume": {"type": "number", "format": "float", "title": "get_universe_types_type_id_packaged_volume", "description": "packaged_volume number"}, "icon_id": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_icon_id", "description": "icon_id integer"}, "capacity": {"type": "number", "format": "float", "title": "get_universe_types_type_id_capacity", "description": "capacity number"}, "portion_size": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_portion_size", "description": "portion_size integer"}, "mass": {"type": "number", "format": "float", "title": "get_universe_types_type_id_mass", "description": "mass number"}, "graphic_id": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_graphic_id", "description": "graphic_id integer"}, "dogma_attributes": {"type": "array", "maxItems": 1000, "items": {"type": "object", "required": ["attribute_id", "value"], "properties": {"attribute_id": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_attribute_id", "description": "attribute_id integer"}, "value": {"type": "number", "format": "float", "title": "get_universe_types_type_id_value", "description": "value number"}}, "title": "get_universe_types_type_id_dogma_attribute", "description": "dogma_attribute object", "x-model": "get_universe_types_type_id_dogma_attribute"}, "title": "get_universe_types_type_id_dogma_attributes", "description": "dogma_attributes array"}, "dogma_effects": {"type": "array", "maxItems": 1000, "items": {"type": "object", "required": ["effect_id", "is_default"], "properties": {"effect_id": {"type": "integer", "format": "int32", "title": "get_universe_types_type_id_effect_id", "description": "effect_id integer"}, "is_default": {"type": "boolean", "title": "get_universe_types_type_id_is_default", "description": "is_default boolean"}}, "title": "get_universe_types_type_id_dogma_effect", "description": "dogma_effect object", "x-model": "get_universe_types_type_id_dogma_effect"}, "title": "get_universe_types_type_id_dogma_effects", "description": "dogma_effects array"}}, "title": "get_universe_types_type_id_ok", "description": "200 ok object", "x-model": "get_universe_types_type_id_ok"}, "headers": {"Content-Language": {"description": "The language used in the response", "type": "string", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"]}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Type not found", "schema": {"type": "object", "title": "get_universe_types_type_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_universe_types_type_id_404_not_found"}}, "x-model": "get_universe_types_type_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_universe_types_type_id", "x-alternate-versions": ["dev", "v3"]}}}} \ No newline at end of file diff --git a/allianceauth/fleetactivitytracking/swagger.json b/allianceauth/fleetactivitytracking/swagger.json index 7d1cab86..4092b658 100644 --- a/allianceauth/fleetactivitytracking/swagger.json +++ b/allianceauth/fleetactivitytracking/swagger.json @@ -1 +1 @@ -{"schemes": ["https"], "securityDefinitions": {"evesso": {"flow": "implicit", "type": "oauth2", "authorizationUrl": "https://login.eveonline.com/oauth/authorize", "scopes": {"esi-corporations.read_contacts.v1": "EVE SSO scope esi-corporations.read_contacts.v1", "esi-search.search_structures.v1": "EVE SSO scope esi-search.search_structures.v1", "esi-corporations.write_structures.v1": "EVE SSO scope esi-corporations.write_structures.v1", "esi-location.read_ship_type.v1": "EVE SSO scope esi-location.read_ship_type.v1", "esi-wallet.read_character_wallet.v1": "EVE SSO scope esi-wallet.read_character_wallet.v1", "esi-calendar.respond_calendar_events.v1": "EVE SSO scope esi-calendar.respond_calendar_events.v1", "esi-characters.read_contacts.v1": "EVE SSO scope esi-characters.read_contacts.v1", "esi-skills.read_skills.v1": "EVE SSO scope esi-skills.read_skills.v1", "esi-calendar.read_calendar_events.v1": "EVE SSO scope esi-calendar.read_calendar_events.v1", "esi-ui.open_window.v1": "EVE SSO scope esi-ui.open_window.v1", "esi-contracts.read_character_contracts.v1": "EVE SSO scope esi-contracts.read_character_contracts.v1", "esi-markets.structure_markets.v1": "EVE SSO scope esi-markets.structure_markets.v1", "esi-assets.read_corporation_assets.v1": "EVE SSO scope esi-assets.read_corporation_assets.v1", "esi-assets.read_assets.v1": "EVE SSO scope esi-assets.read_assets.v1", "esi-wallet.read_corporation_wallets.v1": "EVE SSO scope esi-wallet.read_corporation_wallets.v1", "esi-fittings.read_fittings.v1": "EVE SSO scope esi-fittings.read_fittings.v1", "esi-planets.manage_planets.v1": "EVE SSO scope esi-planets.manage_planets.v1", "esi-characters.read_fatigue.v1": "EVE SSO scope esi-characters.read_fatigue.v1", "esi-characters.read_corporation_roles.v1": "EVE SSO scope esi-characters.read_corporation_roles.v1", "esi-characters.read_chat_channels.v1": "EVE SSO scope esi-characters.read_chat_channels.v1", "esi-bookmarks.read_character_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_character_bookmarks.v1", "esi-fleets.read_fleet.v1": "EVE SSO scope esi-fleets.read_fleet.v1", "esi-clones.read_implants.v1": "EVE SSO scope esi-clones.read_implants.v1", "esi-markets.read_character_orders.v1": "EVE SSO scope esi-markets.read_character_orders.v1", "esi-characters.write_contacts.v1": "EVE SSO scope esi-characters.write_contacts.v1", "esi-characters.read_agents_research.v1": "EVE SSO scope esi-characters.read_agents_research.v1", "esi-industry.read_character_jobs.v1": "EVE SSO scope esi-industry.read_character_jobs.v1", "esi-characters.read_loyalty.v1": "EVE SSO scope esi-characters.read_loyalty.v1", "esi-mail.read_mail.v1": "EVE SSO scope esi-mail.read_mail.v1", "esi-mail.send_mail.v1": "EVE SSO scope esi-mail.send_mail.v1", "esi-location.read_online.v1": "EVE SSO scope esi-location.read_online.v1", "esi-skills.read_skillqueue.v1": "EVE SSO scope esi-skills.read_skillqueue.v1", "esi-characters.read_opportunities.v1": "EVE SSO scope esi-characters.read_opportunities.v1", "esi-killmails.read_corporation_killmails.v1": "EVE SSO scope esi-killmails.read_corporation_killmails.v1", "esi-ui.write_waypoint.v1": "EVE SSO scope esi-ui.write_waypoint.v1", "esi-mail.organize_mail.v1": "EVE SSO scope esi-mail.organize_mail.v1", "esi-characters.read_medals.v1": "EVE SSO scope esi-characters.read_medals.v1", "esi-killmails.read_killmails.v1": "EVE SSO scope esi-killmails.read_killmails.v1", "esi-clones.read_clones.v1": "EVE SSO scope esi-clones.read_clones.v1", "esi-corporations.track_members.v1": "EVE SSO scope esi-corporations.track_members.v1", "esi-universe.read_structures.v1": "EVE SSO scope esi-universe.read_structures.v1", "esi-corporations.read_structures.v1": "EVE SSO scope esi-corporations.read_structures.v1", "esi-corporations.read_corporation_membership.v1": "EVE SSO scope esi-corporations.read_corporation_membership.v1", "esi-location.read_location.v1": "EVE SSO scope esi-location.read_location.v1", "esi-corporations.read_divisions.v1": "EVE SSO scope esi-corporations.read_divisions.v1", "esi-fleets.write_fleet.v1": "EVE SSO scope esi-fleets.write_fleet.v1", "esi-characters.read_blueprints.v1": "EVE SSO scope esi-characters.read_blueprints.v1", "esi-characters.read_standings.v1": "EVE SSO scope esi-characters.read_standings.v1", "esi-characters.read_notifications.v1": "EVE SSO scope esi-characters.read_notifications.v1", "esi-fittings.write_fittings.v1": "EVE SSO scope esi-fittings.write_fittings.v1"}}}, "swagger": "2.0", "info": {"description": "An OpenAPI for EVE Online", "version": "0.5.5", "title": "EVE Swagger Interface"}, "basePath": "/", "paths": {"/v1/characters/{character_id}/ship/": {"get": {"description": "Get the current ship type, name and id\n\n---\n\nThis route is cached for up to 5 seconds", "responses": {"200": {"description": "Get the current ship type, name and id", "examples": {"application/json": {"ship_item_id": 1000000016991, "ship_type_id": 1233, "ship_name": "SPACESHIPS!!!"}}, "schema": {"description": "200 ok object", "properties": {"ship_item_id": {"description": "Item id's are unique to a ship and persist until it is repackaged. This value can be used to track repeated uses of a ship, or detect when a pilot changes into a different instance of the same ship type.", "format": "int64", "type": "integer", "title": "get_characters_character_id_ship_ship_item_id"}, "ship_type_id": {"description": "ship_type_id integer", "format": "int32", "type": "integer", "title": "get_characters_character_id_ship_ship_type_id"}, "ship_name": {"description": "ship_name string", "type": "string", "title": "get_characters_character_id_ship_ship_name"}}, "title": "get_characters_character_id_ship_ok", "type": "object", "required": ["ship_type_id", "ship_item_id", "ship_name"]}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "403": {"description": "Forbidden", "examples": {"application/json": {"error": "Forbidden message"}}, "schema": {"$ref": "#/definitions/forbidden"}}, "500": {"description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}}}, "x-alternate-versions": ["dev", "legacy", "v1"], "parameters": [{"$ref": "#/parameters/character_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/token"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-cached-seconds": 5, "summary": "Get current ship", "security": [{"evesso": ["esi-location.read_ship_type.v1"]}], "operationId": "get_characters_character_id_ship", "tags": ["Location"]}}, "/v3/universe/systems/{system_id}/": {"get": {"description": "Get information on a solar system\n\n---\n\nThis route expires daily at 11:05", "responses": {"200": {"description": "Information about a solar system", "examples": {"application/json": {"position": {"y": 43938227486247170, "x": -91174141133075340, "z": -56482824383339900}, "constellation_id": 20000001, "planets": [{"planet_id": 40000041, "moons": [40000042]}, {"planet_id": 40000043}], "security_status": 0.8462923765182495, "name": "Akpivem", "security_class": "B", "system_id": 30000003, "star_id": 40000040, "stargates": [50000342]}}, "schema": {"description": "200 ok object", "properties": {"position": {"description": "position object", "properties": {"y": {"description": "y number", "format": "float", "type": "number", "title": "get_universe_systems_system_id_y"}, "x": {"description": "x number", "format": "float", "type": "number", "title": "get_universe_systems_system_id_x"}, "z": {"description": "z number", "format": "float", "type": "number", "title": "get_universe_systems_system_id_z"}}, "title": "get_universe_systems_system_id_position", "type": "object", "required": ["x", "y", "z"]}, "constellation_id": {"description": "The constellation this solar system is in", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_constellation_id"}, "planets": {"description": "planets array", "items": {"description": "planet object", "properties": {"planet_id": {"description": "planet_id integer", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_planet_id"}, "moons": {"description": "moons array", "items": {"description": "moon integer", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_moon"}, "maxItems": 1000, "type": "array", "title": "get_universe_systems_system_id_moons"}}, "title": "get_universe_systems_system_id_planet", "type": "object", "required": ["planet_id"]}, "maxItems": 1000, "type": "array", "title": "get_universe_systems_system_id_planets"}, "security_status": {"description": "security_status number", "format": "float", "type": "number", "title": "get_universe_systems_system_id_security_status"}, "name": {"description": "name string", "type": "string", "title": "get_universe_systems_system_id_name"}, "stations": {"description": "stations array", "items": {"description": "station integer", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_station"}, "maxItems": 25, "type": "array", "title": "get_universe_systems_system_id_stations"}, "security_class": {"description": "security_class string", "type": "string", "title": "get_universe_systems_system_id_security_class"}, "system_id": {"description": "system_id integer", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_system_id"}, "star_id": {"description": "star_id integer", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_star_id"}, "stargates": {"description": "stargates array", "items": {"description": "stargate integer", "format": "int32", "type": "integer", "title": "get_universe_systems_system_id_stargate"}, "maxItems": 25, "type": "array", "title": "get_universe_systems_system_id_stargates"}}, "title": "get_universe_systems_system_id_ok", "type": "object", "required": ["star_id", "system_id", "name", "position", "security_status", "constellation_id", "planets"]}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Content-Language": {"description": "The language used in the response", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"], "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "500": {"description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}}, "404": {"description": "Solar system not found", "examples": {"application/json": {"error": "Not found message"}}, "schema": {"description": "Not found", "properties": {"error": {"description": "Not found message", "type": "string", "title": "get_universe_systems_system_id_404_not_found"}}, "type": "object", "title": "get_universe_systems_system_id_not_found"}}}, "x-alternate-versions": ["dev", "v3"], "parameters": [{"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/language"}, {"description": "system_id integer", "format": "int32", "in": "path", "required": true, "name": "system_id", "type": "integer"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "summary": "Get solar system information", "tags": ["Universe"], "operationId": "get_universe_systems_system_id"}}, "/v1/characters/{character_id}/location/": {"get": {"description": "Information about the characters current location. Returns the current solar system id, and also the current station or structure ID if applicable.\n\n---\n\nThis route is cached for up to 5 seconds", "responses": {"200": {"description": "Information about the characters current location. Returns the current solar system id, and also the current station or structure ID if applicable.", "examples": {"application/json": {"solar_system_id": 30002505, "structure_id": 1000000016989}}, "schema": {"description": "200 ok object", "properties": {"station_id": {"description": "station_id integer", "format": "int32", "type": "integer", "title": "get_characters_character_id_location_station_id"}, "solar_system_id": {"description": "solar_system_id integer", "format": "int32", "type": "integer", "title": "get_characters_character_id_location_solar_system_id"}, "structure_id": {"description": "structure_id integer", "format": "int64", "type": "integer", "title": "get_characters_character_id_location_structure_id"}}, "title": "get_characters_character_id_location_ok", "type": "object", "required": ["solar_system_id"]}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "403": {"description": "Forbidden", "examples": {"application/json": {"error": "Forbidden message"}}, "schema": {"$ref": "#/definitions/forbidden"}}, "500": {"description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}}}, "x-alternate-versions": ["dev", "legacy", "v1"], "parameters": [{"$ref": "#/parameters/character_id"}, {"$ref": "#/parameters/datasource"}, {"$ref": "#/parameters/token"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-cached-seconds": 5, "summary": "Get character location", "security": [{"evesso": ["esi-location.read_location.v1"]}], "operationId": "get_characters_character_id_location", "tags": ["Location"]}}, "/v2/universe/stations/{station_id}/": {"get": {"description": "Get information on a station\n\n---\n\nThis route is cached for up to 300 seconds", "responses": {"200": {"description": "Information about a station", "examples": {"application/json": {"race_id": 1, "office_rental_cost": 10000, "system_id": 30000148, "services": ["courier-missions", "reprocessing-plant", "market", "repair-facilities", "fitting", "news", "storage", "insurance", "docking", "office-rental", "loyalty-point-store", "navy-offices"], "station_id": 60000277, "reprocessing_stations_take": 0.05, "reprocessing_efficiency": 0.5, "position": {"y": 2771804160, "x": 165632286720, "z": -2455331266560}, "owner": 1000003, "name": "Jakanerva III - Moon 15 - Prompt Delivery Storage", "type_id": 1531, "max_dockable_ship_volume": 50000000}}, "schema": {"description": "200 ok object", "properties": {"race_id": {"description": "race_id integer", "format": "int32", "type": "integer", "title": "get_universe_stations_station_id_race_id"}, "office_rental_cost": {"description": "office_rental_cost number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_office_rental_cost"}, "system_id": {"description": "The solar system this station is in", "format": "int32", "type": "integer", "title": "get_universe_stations_station_id_system_id"}, "services": {"description": "services array", "items": {"description": "service string", "enum": ["bounty-missions", "assasination-missions", "courier-missions", "interbus", "reprocessing-plant", "refinery", "market", "black-market", "stock-exchange", "cloning", "surgery", "dna-therapy", "repair-facilities", "factory", "labratory", "gambling", "fitting", "paintshop", "news", "storage", "insurance", "docking", "office-rental", "jump-clone-facility", "loyalty-point-store", "navy-offices", "security-offices"], "type": "string", "title": "get_universe_stations_station_id_service"}, "maxItems": 30, "type": "array", "title": "get_universe_stations_station_id_services"}, "station_id": {"description": "station_id integer", "format": "int32", "type": "integer", "title": "get_universe_stations_station_id_station_id"}, "reprocessing_stations_take": {"description": "reprocessing_stations_take number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_reprocessing_stations_take"}, "reprocessing_efficiency": {"description": "reprocessing_efficiency number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_reprocessing_efficiency"}, "position": {"description": "position object", "properties": {"y": {"description": "y number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_y"}, "x": {"description": "x number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_x"}, "z": {"description": "z number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_z"}}, "title": "get_universe_stations_station_id_position", "type": "object", "required": ["x", "y", "z"]}, "owner": {"description": "ID of the corporation that controls this station", "format": "int32", "type": "integer", "title": "get_universe_stations_station_id_owner"}, "name": {"description": "name string", "type": "string", "title": "get_universe_stations_station_id_name"}, "type_id": {"description": "type_id integer", "format": "int32", "type": "integer", "title": "get_universe_stations_station_id_type_id"}, "max_dockable_ship_volume": {"description": "max_dockable_ship_volume number", "format": "float", "type": "number", "title": "get_universe_stations_station_id_max_dockable_ship_volume"}}, "title": "get_universe_stations_station_id_ok", "type": "object", "required": ["station_id", "name", "type_id", "position", "system_id", "reprocessing_efficiency", "reprocessing_stations_take", "max_dockable_ship_volume", "office_rental_cost", "services"]}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "500": {"description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}}, "404": {"description": "Station not found", "examples": {"application/json": {"error": "Not found message"}}, "schema": {"description": "Not found", "properties": {"error": {"description": "Not found message", "type": "string", "title": "get_universe_stations_station_id_404_not_found"}}, "type": "object", "title": "get_universe_stations_station_id_not_found"}}}, "x-alternate-versions": ["dev", "v2"], "parameters": [{"$ref": "#/parameters/datasource"}, {"description": "station_id integer", "format": "int32", "in": "path", "required": true, "name": "station_id", "type": "integer"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-cached-seconds": 300, "summary": "Get station information", "tags": ["Universe"], "operationId": "get_universe_stations_station_id"}}, "/v1/universe/structures/{structure_id}/": {"get": {"description": "Returns information on requested structure, if you are on the ACL. Otherwise, returns \"Forbidden\" for all inputs.\n\n---\n\nThis route is cached for up to 3600 seconds", "responses": {"200": {"description": "Data about a structure", "examples": {"application/json": {"solar_system_id": 30000142, "name": "V-3YG7 VI - The Capital"}}, "schema": {"description": "200 ok object", "properties": {"position": {"description": "Coordinates of the structure in Cartesian space relative to the Sun, in metres.\n", "properties": {"y": {"description": "y number", "format": "float", "type": "number", "title": "get_universe_structures_structure_id_y"}, "x": {"description": "x number", "format": "float", "type": "number", "title": "get_universe_structures_structure_id_x"}, "z": {"description": "z number", "format": "float", "type": "number", "title": "get_universe_structures_structure_id_z"}}, "title": "get_universe_structures_structure_id_position", "type": "object", "required": ["x", "y", "z"]}, "type_id": {"description": "type_id integer", "format": "int32", "type": "integer", "title": "get_universe_structures_structure_id_type_id"}, "solar_system_id": {"description": "solar_system_id integer", "format": "int32", "type": "integer", "title": "get_universe_structures_structure_id_solar_system_id"}, "name": {"description": "The full name of the structure", "type": "string", "title": "get_universe_structures_structure_id_name"}}, "title": "get_universe_structures_structure_id_ok", "type": "object", "required": ["name", "solar_system_id"]}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "403": {"description": "Forbidden", "examples": {"application/json": {"error": "Forbidden message"}}, "schema": {"$ref": "#/definitions/forbidden"}}, "500": {"description": "Internal server error", "examples": {"application/json": {"error": "Internal server error message"}}, "schema": {"$ref": "#/definitions/internal_server_error"}}, "404": {"description": "Structure not found", "examples": {"application/json": {"error": "Not found message"}}, "schema": {"description": "Not found", "properties": {"error": {"description": "Not found message", "type": "string", "title": "get_universe_structures_structure_id_404_not_found"}}, "type": "object", "title": "get_universe_structures_structure_id_not_found"}}}, "x-alternate-versions": ["dev", "legacy", "v1"], "parameters": [{"$ref": "#/parameters/datasource"}, {"description": "An Eve structure ID", "format": "int64", "in": "path", "required": true, "name": "structure_id", "type": "integer"}, {"$ref": "#/parameters/token"}, {"$ref": "#/parameters/user_agent"}, {"$ref": "#/parameters/X-User-Agent"}], "x-cached-seconds": 3600, "summary": "Get structure information", "security": [{"evesso": ["esi-universe.read_structures.v1"]}], "operationId": "get_universe_structures_structure_id", "tags": ["Universe"]}}}, "host": "esi.tech.ccp.is", "parameters": {"page": {"description": "Which page of results to return", "format": "int32", "default": 1, "name": "page", "in": "query", "type": "integer"}, "alliance_id": {"description": "An EVE alliance ID", "format": "int32", "in": "path", "required": true, "name": "alliance_id", "type": "integer"}, "character_id": {"description": "An EVE character ID", "format": "int32", "in": "path", "required": true, "name": "character_id", "type": "integer"}, "X-User-Agent": {"description": "Client identifier, takes precedence over User-Agent", "name": "X-User-Agent", "type": "string", "in": "header"}, "datasource": {"description": "The server name you would like data from", "default": "tranquility", "name": "datasource", "enum": ["tranquility", "singularity"], "in": "query", "type": "string"}, "token": {"description": "Access token to use if unable to set a header", "name": "token", "type": "string", "in": "query"}, "language": {"description": "Language to use in the response", "default": "en-us", "name": "language", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"], "in": "query", "type": "string"}, "corporation_id": {"description": "An EVE corporation ID", "format": "int32", "in": "path", "required": true, "name": "corporation_id", "type": "integer"}, "user_agent": {"description": "Client identifier, takes precedence over headers", "name": "user_agent", "type": "string", "in": "query"}}, "definitions": {"internal_server_error": {"description": "Internal server error model", "properties": {"error": {"description": "Internal server error message", "type": "string"}}, "title": "Internal server error", "type": "object", "required": ["error"]}, "forbidden": {"description": "Forbidden model", "properties": {"error": {"description": "Forbidden message", "type": "string"}, "sso_status": {"description": "Status code received from SSO", "type": "integer"}}, "title": "Forbidden", "type": "object", "required": ["error"]}}, "produces": ["application/json"]} \ No newline at end of file +{"swagger": "2.0", "info": {"title": "EVE Swagger Interface", "description": "An OpenAPI for EVE Online", "version": "0.8.0"}, "host": "esi.tech.ccp.is", "schemes": ["https"], "produces": ["application/json"], "securityDefinitions": {"evesso": {"type": "oauth2", "authorizationUrl": "https://login.eveonline.com/oauth/authorize", "flow": "implicit", "scopes": {"esi-alliances.read_contacts.v1": "EVE SSO scope esi-alliances.read_contacts.v1", "esi-assets.read_assets.v1": "EVE SSO scope esi-assets.read_assets.v1", "esi-assets.read_corporation_assets.v1": "EVE SSO scope esi-assets.read_corporation_assets.v1", "esi-bookmarks.read_character_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_character_bookmarks.v1", "esi-bookmarks.read_corporation_bookmarks.v1": "EVE SSO scope esi-bookmarks.read_corporation_bookmarks.v1", "esi-calendar.read_calendar_events.v1": "EVE SSO scope esi-calendar.read_calendar_events.v1", "esi-calendar.respond_calendar_events.v1": "EVE SSO scope esi-calendar.respond_calendar_events.v1", "esi-characters.read_agents_research.v1": "EVE SSO scope esi-characters.read_agents_research.v1", "esi-characters.read_blueprints.v1": "EVE SSO scope esi-characters.read_blueprints.v1", "esi-characters.read_chat_channels.v1": "EVE SSO scope esi-characters.read_chat_channels.v1", "esi-characters.read_contacts.v1": "EVE SSO scope esi-characters.read_contacts.v1", "esi-characters.read_corporation_roles.v1": "EVE SSO scope esi-characters.read_corporation_roles.v1", "esi-characters.read_fatigue.v1": "EVE SSO scope esi-characters.read_fatigue.v1", "esi-characters.read_fw_stats.v1": "EVE SSO scope esi-characters.read_fw_stats.v1", "esi-characters.read_loyalty.v1": "EVE SSO scope esi-characters.read_loyalty.v1", "esi-characters.read_medals.v1": "EVE SSO scope esi-characters.read_medals.v1", "esi-characters.read_notifications.v1": "EVE SSO scope esi-characters.read_notifications.v1", "esi-characters.read_opportunities.v1": "EVE SSO scope esi-characters.read_opportunities.v1", "esi-characters.read_standings.v1": "EVE SSO scope esi-characters.read_standings.v1", "esi-characters.read_titles.v1": "EVE SSO scope esi-characters.read_titles.v1", "esi-characters.write_contacts.v1": "EVE SSO scope esi-characters.write_contacts.v1", "esi-characterstats.read.v1": "EVE SSO scope esi-characterstats.read.v1", "esi-clones.read_clones.v1": "EVE SSO scope esi-clones.read_clones.v1", "esi-clones.read_implants.v1": "EVE SSO scope esi-clones.read_implants.v1", "esi-contracts.read_character_contracts.v1": "EVE SSO scope esi-contracts.read_character_contracts.v1", "esi-contracts.read_corporation_contracts.v1": "EVE SSO scope esi-contracts.read_corporation_contracts.v1", "esi-corporations.read_blueprints.v1": "EVE SSO scope esi-corporations.read_blueprints.v1", "esi-corporations.read_contacts.v1": "EVE SSO scope esi-corporations.read_contacts.v1", "esi-corporations.read_container_logs.v1": "EVE SSO scope esi-corporations.read_container_logs.v1", "esi-corporations.read_corporation_membership.v1": "EVE SSO scope esi-corporations.read_corporation_membership.v1", "esi-corporations.read_divisions.v1": "EVE SSO scope esi-corporations.read_divisions.v1", "esi-corporations.read_facilities.v1": "EVE SSO scope esi-corporations.read_facilities.v1", "esi-corporations.read_fw_stats.v1": "EVE SSO scope esi-corporations.read_fw_stats.v1", "esi-corporations.read_medals.v1": "EVE SSO scope esi-corporations.read_medals.v1", "esi-corporations.read_outposts.v1": "EVE SSO scope esi-corporations.read_outposts.v1", "esi-corporations.read_standings.v1": "EVE SSO scope esi-corporations.read_standings.v1", "esi-corporations.read_starbases.v1": "EVE SSO scope esi-corporations.read_starbases.v1", "esi-corporations.read_structures.v1": "EVE SSO scope esi-corporations.read_structures.v1", "esi-corporations.read_titles.v1": "EVE SSO scope esi-corporations.read_titles.v1", "esi-corporations.track_members.v1": "EVE SSO scope esi-corporations.track_members.v1", "esi-fittings.read_fittings.v1": "EVE SSO scope esi-fittings.read_fittings.v1", "esi-fittings.write_fittings.v1": "EVE SSO scope esi-fittings.write_fittings.v1", "esi-fleets.read_fleet.v1": "EVE SSO scope esi-fleets.read_fleet.v1", "esi-fleets.write_fleet.v1": "EVE SSO scope esi-fleets.write_fleet.v1", "esi-industry.read_character_jobs.v1": "EVE SSO scope esi-industry.read_character_jobs.v1", "esi-industry.read_character_mining.v1": "EVE SSO scope esi-industry.read_character_mining.v1", "esi-industry.read_corporation_jobs.v1": "EVE SSO scope esi-industry.read_corporation_jobs.v1", "esi-industry.read_corporation_mining.v1": "EVE SSO scope esi-industry.read_corporation_mining.v1", "esi-killmails.read_corporation_killmails.v1": "EVE SSO scope esi-killmails.read_corporation_killmails.v1", "esi-killmails.read_killmails.v1": "EVE SSO scope esi-killmails.read_killmails.v1", "esi-location.read_location.v1": "EVE SSO scope esi-location.read_location.v1", "esi-location.read_online.v1": "EVE SSO scope esi-location.read_online.v1", "esi-location.read_ship_type.v1": "EVE SSO scope esi-location.read_ship_type.v1", "esi-mail.organize_mail.v1": "EVE SSO scope esi-mail.organize_mail.v1", "esi-mail.read_mail.v1": "EVE SSO scope esi-mail.read_mail.v1", "esi-mail.send_mail.v1": "EVE SSO scope esi-mail.send_mail.v1", "esi-markets.read_character_orders.v1": "EVE SSO scope esi-markets.read_character_orders.v1", "esi-markets.read_corporation_orders.v1": "EVE SSO scope esi-markets.read_corporation_orders.v1", "esi-markets.structure_markets.v1": "EVE SSO scope esi-markets.structure_markets.v1", "esi-planets.manage_planets.v1": "EVE SSO scope esi-planets.manage_planets.v1", "esi-planets.read_customs_offices.v1": "EVE SSO scope esi-planets.read_customs_offices.v1", "esi-search.search_structures.v1": "EVE SSO scope esi-search.search_structures.v1", "esi-skills.read_skillqueue.v1": "EVE SSO scope esi-skills.read_skillqueue.v1", "esi-skills.read_skills.v1": "EVE SSO scope esi-skills.read_skills.v1", "esi-ui.open_window.v1": "EVE SSO scope esi-ui.open_window.v1", "esi-ui.write_waypoint.v1": "EVE SSO scope esi-ui.write_waypoint.v1", "esi-universe.read_structures.v1": "EVE SSO scope esi-universe.read_structures.v1", "esi-wallet.read_character_wallet.v1": "EVE SSO scope esi-wallet.read_character_wallet.v1", "esi-wallet.read_corporation_wallets.v1": "EVE SSO scope esi-wallet.read_corporation_wallets.v1"}}}, "parameters": {"datasource": {"name": "datasource", "description": "The server name you would like data from", "in": "query", "type": "string", "default": "tranquility", "enum": ["tranquility", "singularity"]}, "user_agent": {"name": "user_agent", "description": "Client identifier, takes precedence over headers", "in": "query", "type": "string"}, "X-User-Agent": {"name": "X-User-Agent", "description": "Client identifier, takes precedence over User-Agent", "in": "header", "type": "string"}, "page": {"name": "page", "description": "Which page of results to return", "in": "query", "type": "integer", "format": "int32", "default": 1}, "token": {"name": "token", "description": "Access token to use if unable to set a header", "in": "query", "type": "string"}, "character_id": {"description": "An EVE character ID", "format": "int32", "in": "path", "minimum": 1, "name": "character_id", "required": true, "type": "integer"}, "corporation_id": {"description": "An EVE corporation ID", "format": "int32", "in": "path", "minimum": 1, "name": "corporation_id", "required": true, "type": "integer"}, "language": {"name": "language", "description": "Language to use in the response", "in": "query", "type": "string", "default": "en-us", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"]}, "alliance_id": {"description": "An EVE alliance ID", "format": "int32", "in": "path", "minimum": 1, "name": "alliance_id", "required": true, "type": "integer"}}, "definitions": {"unauthorized": {"type": "object", "description": "Unauthorized model", "title": "Unauthorized", "required": ["error"], "properties": {"error": {"type": "string", "description": "Unauthorized message"}}, "x-model": "Unauthorized"}, "forbidden": {"type": "object", "description": "Forbidden model", "title": "Forbidden", "required": ["error"], "properties": {"error": {"type": "string", "description": "Forbidden message"}, "sso_status": {"type": "integer", "description": "Status code received from SSO"}}, "x-model": "Forbidden"}, "bad_request": {"type": "object", "description": "Bad request model", "title": "Bad request", "required": ["error"], "properties": {"error": {"type": "string", "description": "Bad request message"}}, "x-model": "Bad request"}, "internal_server_error": {"type": "object", "description": "Internal server error model", "title": "Internal server error", "required": ["error"], "properties": {"error": {"type": "string", "description": "Internal server error message"}}, "x-model": "Internal server error"}, "bad_gateway": {"type": "object", "description": "Bad gateway model", "title": "Bad gateway", "required": ["error"], "properties": {"error": {"type": "string", "description": "Bad gateway message"}}, "x-model": "Bad gateway"}, "service_unavailable": {"type": "object", "description": "Service unavailable model", "title": "Service unavailable", "required": ["error"], "properties": {"error": {"type": "string", "description": "Service unavailable message"}}, "x-model": "Service unavailable"}}, "paths": {"/v1/characters/{character_id}/location/": {"get": {"description": "Information about the characters current location. Returns the current solar system id, and also the current station or structure ID if applicable.\n\n---\n\nThis route is cached for up to 5 seconds", "summary": "Get character location", "tags": ["Location"], "parameters": [{"$ref": "#/parameters/character_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/token", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Information about the characters current location. Returns the current solar system id, and also the current station or structure ID if applicable.", "examples": {"application/json": {"solar_system_id": 30002505, "structure_id": 1000000016989}}, "schema": {"type": "object", "required": ["solar_system_id"], "properties": {"solar_system_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_location_solar_system_id", "description": "solar_system_id integer"}, "station_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_location_station_id", "description": "station_id integer"}, "structure_id": {"type": "integer", "format": "int64", "title": "get_characters_character_id_location_structure_id", "description": "structure_id integer"}}, "title": "get_characters_character_id_location_ok", "description": "200 ok object", "x-model": "get_characters_character_id_location_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "401": {"description": "Unauthorized", "schema": {"$ref": "#/definitions/unauthorized", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Unauthorized message"}}}, "403": {"description": "Forbidden", "schema": {"$ref": "#/definitions/forbidden", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Forbidden message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "security": [{"evesso": ["esi-location.read_location.v1"]}], "operationId": "get_characters_character_id_location", "x-cached-seconds": 5, "x-alternate-versions": ["dev", "legacy", "v1"]}}, "/v1/characters/{character_id}/ship/": {"get": {"description": "Get the current ship type, name and id\n\n---\n\nThis route is cached for up to 5 seconds", "summary": "Get current ship", "tags": ["Location"], "parameters": [{"$ref": "#/parameters/character_id", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/token", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Get the current ship type, name and id", "examples": {"application/json": {"ship_type_id": 1233, "ship_name": "SPACESHIPS!!!", "ship_item_id": 1000000016991}}, "schema": {"type": "object", "required": ["ship_type_id", "ship_item_id", "ship_name"], "properties": {"ship_type_id": {"type": "integer", "format": "int32", "title": "get_characters_character_id_ship_ship_type_id", "description": "ship_type_id integer"}, "ship_item_id": {"type": "integer", "format": "int64", "description": "Item id's are unique to a ship and persist until it is repackaged. This value can be used to track repeated uses of a ship, or detect when a pilot changes into a different instance of the same ship type.", "title": "get_characters_character_id_ship_ship_item_id"}, "ship_name": {"type": "string", "title": "get_characters_character_id_ship_ship_name", "description": "ship_name string"}}, "title": "get_characters_character_id_ship_ok", "description": "200 ok object", "x-model": "get_characters_character_id_ship_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "401": {"description": "Unauthorized", "schema": {"$ref": "#/definitions/unauthorized", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Unauthorized message"}}}, "403": {"description": "Forbidden", "schema": {"$ref": "#/definitions/forbidden", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Forbidden message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "security": [{"evesso": ["esi-location.read_ship_type.v1"]}], "operationId": "get_characters_character_id_ship", "x-cached-seconds": 5, "x-alternate-versions": ["dev", "legacy", "v1"]}}, "/v2/universe/stations/{station_id}/": {"get": {"description": "Get information on a station\n\n---\n\nThis route is cached for up to 300 seconds", "summary": "Get station information", "tags": ["Universe"], "parameters": [{"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"name": "station_id", "in": "path", "required": true, "type": "integer", "format": "int32", "description": "station_id integer"}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Information about a station", "examples": {"application/json": {"station_id": 60000277, "name": "Jakanerva III - Moon 15 - Prompt Delivery Storage", "type_id": 1531, "position": {"x": 165632286720, "y": 2771804160, "z": -2455331266560}, "system_id": 30000148, "reprocessing_efficiency": 0.5, "reprocessing_stations_take": 0.05, "max_dockable_ship_volume": 50000000, "office_rental_cost": 10000, "services": ["courier-missions", "reprocessing-plant", "market", "repair-facilities", "fitting", "news", "storage", "insurance", "docking", "office-rental", "loyalty-point-store", "navy-offices"], "owner": 1000003, "race_id": 1}}, "schema": {"type": "object", "required": ["station_id", "name", "type_id", "position", "system_id", "reprocessing_efficiency", "reprocessing_stations_take", "max_dockable_ship_volume", "office_rental_cost", "services"], "properties": {"station_id": {"type": "integer", "format": "int32", "title": "get_universe_stations_station_id_station_id", "description": "station_id integer"}, "name": {"type": "string", "title": "get_universe_stations_station_id_name", "description": "name string"}, "owner": {"type": "integer", "format": "int32", "description": "ID of the corporation that controls this station", "title": "get_universe_stations_station_id_owner"}, "type_id": {"type": "integer", "format": "int32", "title": "get_universe_stations_station_id_type_id", "description": "type_id integer"}, "race_id": {"type": "integer", "format": "int32", "title": "get_universe_stations_station_id_race_id", "description": "race_id integer"}, "position": {"type": "object", "required": ["x", "y", "z"], "properties": {"x": {"type": "number", "format": "double", "title": "get_universe_stations_station_id_x", "description": "x number"}, "y": {"type": "number", "format": "double", "title": "get_universe_stations_station_id_y", "description": "y number"}, "z": {"type": "number", "format": "double", "title": "get_universe_stations_station_id_z", "description": "z number"}}, "title": "get_universe_stations_station_id_position", "description": "position object", "x-model": "get_universe_stations_station_id_position"}, "system_id": {"type": "integer", "format": "int32", "description": "The solar system this station is in", "title": "get_universe_stations_station_id_system_id"}, "reprocessing_efficiency": {"type": "number", "format": "float", "title": "get_universe_stations_station_id_reprocessing_efficiency", "description": "reprocessing_efficiency number"}, "reprocessing_stations_take": {"type": "number", "format": "float", "title": "get_universe_stations_station_id_reprocessing_stations_take", "description": "reprocessing_stations_take number"}, "max_dockable_ship_volume": {"type": "number", "format": "float", "title": "get_universe_stations_station_id_max_dockable_ship_volume", "description": "max_dockable_ship_volume number"}, "office_rental_cost": {"type": "number", "format": "float", "title": "get_universe_stations_station_id_office_rental_cost", "description": "office_rental_cost number"}, "services": {"type": "array", "maxItems": 30, "items": {"type": "string", "enum": ["bounty-missions", "assasination-missions", "courier-missions", "interbus", "reprocessing-plant", "refinery", "market", "black-market", "stock-exchange", "cloning", "surgery", "dna-therapy", "repair-facilities", "factory", "labratory", "gambling", "fitting", "paintshop", "news", "storage", "insurance", "docking", "office-rental", "jump-clone-facility", "loyalty-point-store", "navy-offices", "security-offices"], "title": "get_universe_stations_station_id_service", "description": "service string"}, "title": "get_universe_stations_station_id_services", "description": "services array"}}, "title": "get_universe_stations_station_id_ok", "description": "200 ok object", "x-model": "get_universe_stations_station_id_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Station not found", "schema": {"type": "object", "title": "get_universe_stations_station_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_universe_stations_station_id_404_not_found"}}, "x-model": "get_universe_stations_station_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_universe_stations_station_id", "x-cached-seconds": 300, "x-alternate-versions": ["dev", "v2"]}}, "/v1/universe/structures/{structure_id}/": {"get": {"description": "Returns information on requested structure, if you are on the ACL. Otherwise, returns \"Forbidden\" for all inputs.\n\n---\n\nThis route is cached for up to 3600 seconds", "summary": "Get structure information", "tags": ["Universe"], "parameters": [{"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"name": "structure_id", "in": "path", "description": "An Eve structure ID", "required": true, "type": "integer", "format": "int64"}, {"$ref": "#/parameters/token", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Data about a structure", "examples": {"application/json": {"name": "V-3YG7 VI - The Capital", "solar_system_id": 30000142}}, "schema": {"type": "object", "required": ["name", "solar_system_id"], "properties": {"name": {"type": "string", "description": "The full name of the structure", "title": "get_universe_structures_structure_id_name"}, "solar_system_id": {"type": "integer", "format": "int32", "title": "get_universe_structures_structure_id_solar_system_id", "description": "solar_system_id integer"}, "type_id": {"type": "integer", "format": "int32", "title": "get_universe_structures_structure_id_type_id", "description": "type_id integer"}, "position": {"type": "object", "description": "Coordinates of the structure in Cartesian space relative to the Sun, in metres.\n", "required": ["x", "y", "z"], "properties": {"x": {"type": "number", "format": "double", "title": "get_universe_structures_structure_id_x", "description": "x number"}, "y": {"type": "number", "format": "double", "title": "get_universe_structures_structure_id_y", "description": "y number"}, "z": {"type": "number", "format": "double", "title": "get_universe_structures_structure_id_z", "description": "z number"}}, "title": "get_universe_structures_structure_id_position", "x-model": "get_universe_structures_structure_id_position"}}, "title": "get_universe_structures_structure_id_ok", "description": "200 ok object", "x-model": "get_universe_structures_structure_id_ok"}, "headers": {"Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Structure not found", "schema": {"type": "object", "title": "get_universe_structures_structure_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_universe_structures_structure_id_404_not_found"}}, "x-model": "get_universe_structures_structure_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "401": {"description": "Unauthorized", "schema": {"$ref": "#/definitions/unauthorized", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Unauthorized message"}}}, "403": {"description": "Forbidden", "schema": {"$ref": "#/definitions/forbidden", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Forbidden message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "security": [{"evesso": ["esi-universe.read_structures.v1"]}], "operationId": "get_universe_structures_structure_id", "x-cached-seconds": 3600, "x-alternate-versions": ["dev", "legacy", "v1"]}}, "/v3/universe/systems/{system_id}/": {"get": {"description": "Get information on a solar system\n\n---\n\nThis route expires daily at 11:05", "summary": "Get solar system information", "tags": ["Universe"], "parameters": [{"$ref": "#/parameters/datasource", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/language", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"name": "system_id", "in": "path", "required": true, "type": "integer", "format": "int32", "description": "system_id integer"}, {"$ref": "#/parameters/user_agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, {"$ref": "#/parameters/X-User-Agent", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}], "responses": {"200": {"description": "Information about a solar system", "examples": {"application/json": {"system_id": 30000003, "name": "Akpivem", "position": {"x": -91174141133075340, "y": 43938227486247170, "z": -56482824383339900}, "security_status": 0.8462923765, "constellation_id": 20000001, "planets": [{"planet_id": 40000041, "moons": [40000042]}, {"planet_id": 40000043}], "stargates": [50000342], "star_id": 40000040, "security_class": "B"}}, "schema": {"type": "object", "required": ["star_id", "system_id", "name", "position", "security_status", "constellation_id", "planets"], "properties": {"star_id": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_star_id", "description": "star_id integer"}, "system_id": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_system_id", "description": "system_id integer"}, "name": {"type": "string", "title": "get_universe_systems_system_id_name", "description": "name string"}, "position": {"type": "object", "required": ["x", "y", "z"], "properties": {"x": {"type": "number", "format": "double", "title": "get_universe_systems_system_id_x", "description": "x number"}, "y": {"type": "number", "format": "double", "title": "get_universe_systems_system_id_y", "description": "y number"}, "z": {"type": "number", "format": "double", "title": "get_universe_systems_system_id_z", "description": "z number"}}, "title": "get_universe_systems_system_id_position", "description": "position object", "x-model": "get_universe_systems_system_id_position"}, "security_status": {"type": "number", "format": "float", "title": "get_universe_systems_system_id_security_status", "description": "security_status number"}, "security_class": {"type": "string", "title": "get_universe_systems_system_id_security_class", "description": "security_class string"}, "constellation_id": {"type": "integer", "format": "int32", "description": "The constellation this solar system is in", "title": "get_universe_systems_system_id_constellation_id"}, "planets": {"type": "array", "maxItems": 1000, "items": {"type": "object", "required": ["planet_id"], "properties": {"planet_id": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_planet_id", "description": "planet_id integer"}, "moons": {"type": "array", "maxItems": 1000, "items": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_moon", "description": "moon integer"}, "title": "get_universe_systems_system_id_moons", "description": "moons array"}, "asteroid_belts": {"type": "array", "maxItems": 100, "items": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_asteroid_belt", "description": "asteroid_belt integer"}, "title": "get_universe_systems_system_id_asteroid_belts", "description": "asteroid_belts array"}}, "title": "get_universe_systems_system_id_planet", "description": "planet object", "x-model": "get_universe_systems_system_id_planet"}, "title": "get_universe_systems_system_id_planets", "description": "planets array"}, "stargates": {"type": "array", "maxItems": 25, "items": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_stargate", "description": "stargate integer"}, "title": "get_universe_systems_system_id_stargates", "description": "stargates array"}, "stations": {"type": "array", "maxItems": 25, "items": {"type": "integer", "format": "int32", "title": "get_universe_systems_system_id_station", "description": "station integer"}, "title": "get_universe_systems_system_id_stations", "description": "stations array"}}, "title": "get_universe_systems_system_id_ok", "description": "200 ok object", "x-model": "get_universe_systems_system_id_ok"}, "headers": {"Content-Language": {"description": "The language used in the response", "type": "string", "enum": ["de", "en-us", "fr", "ja", "ru", "zh"]}, "Cache-Control": {"description": "The caching mechanism used", "type": "string"}, "Last-Modified": {"description": "RFC7231 formatted datetime string", "type": "string"}, "Expires": {"description": "RFC7231 formatted datetime string", "type": "string"}}}, "404": {"description": "Solar system not found", "schema": {"type": "object", "title": "get_universe_systems_system_id_not_found", "description": "Not found", "properties": {"error": {"type": "string", "description": "Not found message", "title": "get_universe_systems_system_id_404_not_found"}}, "x-model": "get_universe_systems_system_id_not_found"}, "examples": {"application/json": {"error": "Not found message"}}}, "400": {"description": "Bad request", "schema": {"$ref": "#/definitions/bad_request", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad request message"}}}, "500": {"description": "Internal server error", "schema": {"$ref": "#/definitions/internal_server_error", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Internal server error message"}}}, "502": {"description": "Bad gateway", "schema": {"$ref": "#/definitions/bad_gateway", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Bad gateway message"}}}, "503": {"description": "Service unavailable", "schema": {"$ref": "#/definitions/service_unavailable", "x-scope": ["https://esi.tech.ccp.is/_latest/swagger.json"]}, "examples": {"application/json": {"error": "Service unavailable message"}}}}, "operationId": "get_universe_systems_system_id", "x-alternate-versions": ["dev", "v3"]}}}} \ No newline at end of file From bee69cc250547f22b8d3f120df11e5a4930f9a18 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Thu, 19 Apr 2018 17:10:38 -0400 Subject: [PATCH 34/47] User is created inactive. When users were created they started active, then were changed to inactive and saved. This triggered service account validation logic which is silly to be running on brand new users. I hated seeing those logging messages so now it doesn't happen. At the same time I do love logging messages so I added some to the authentication process. --- allianceauth/authentication/backends.py | 14 +++++++++++--- 1 file changed, 11 insertions(+), 3 deletions(-) diff --git a/allianceauth/authentication/backends.py b/allianceauth/authentication/backends.py index a37e4afd..69c516b8 100644 --- a/allianceauth/authentication/backends.py +++ b/allianceauth/authentication/backends.py @@ -1,10 +1,13 @@ from django.contrib.auth.backends import ModelBackend from django.contrib.auth.models import Permission from django.contrib.auth.models import User - +import logging from .models import UserProfile, CharacterOwnership, OwnershipRecord +logger = logging.getLogger(__name__) + + class StateBackend(ModelBackend): @staticmethod def _get_state_permissions(user_obj): @@ -30,14 +33,17 @@ class StateBackend(ModelBackend): try: ownership = CharacterOwnership.objects.get(character__character_id=token.character_id) if ownership.owner_hash == token.character_owner_hash: + logger.debug('Authenticating {0} by ownership of character {1}'.format(ownership.user, token.character_name)) return ownership.user else: + logger.debug('{0} has changed ownership. Creating new user account.'.format(token.character_name)) ownership.delete() return self.create_user(token) except CharacterOwnership.DoesNotExist: try: # insecure legacy main check for pre-sso registration auth installs profile = UserProfile.objects.get(main_character__character_id=token.character_id) + logger.debug('Authenticating {0} by their main character {1} without active ownership.'.format(profile.user, profile.main_character)) # attach an ownership token.user = profile.user CharacterOwnership.objects.create_by_token(token) @@ -50,23 +56,25 @@ class StateBackend(ModelBackend): user = records[0].user token.user = user co = CharacterOwnership.objects.create_by_token(token) + logger.debug('Authenticating {0} by matching owner hash record of character {1}'.format(user, co.character)) if not user.profile.main_character: # set this as their main by default if they have none user.profile.main_character = co.character user.profile.save() return user + logger.debug('Unable to authenticate character {0}. Creating new user.'.format(token.character_name)) return self.create_user(token) def create_user(self, token): username = self.iterate_username(token.character_name) # build unique username off character name - user = User.objects.create_user(username) + user = User.objects.create_user(username, is_active=False) # prevent login until email set user.set_unusable_password() # prevent login via password - user.is_active = False # prevent login until email set user.save() token.user = user co = CharacterOwnership.objects.create_by_token(token) # assign ownership to this user user.profile.main_character = co.character # assign main character as token character user.profile.save() + logger.debug('Created new user {0}'.format(user)) return user @staticmethod From b65ccac58fff83c975f38f7cecdb051d8bd8e906 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Thu, 19 Apr 2018 17:13:07 -0400 Subject: [PATCH 35/47] Revoke CharacterOwnership on token deletion. I'm pretty sure this is what I meant to do initially. I created the OwnershipRecord system under the assumption that CharacterOwnership models were being deleted when they could no longer be validated. That turned out not to be the case - only main characters were rest. This ensures they are deleted when they can no longer be validated. --- allianceauth/authentication/signals.py | 18 ++++-------------- 1 file changed, 4 insertions(+), 14 deletions(-) diff --git a/allianceauth/authentication/signals.py b/allianceauth/authentication/signals.py index d8e73fdf..bd062b56 100644 --- a/allianceauth/authentication/signals.py +++ b/allianceauth/authentication/signals.py @@ -112,20 +112,10 @@ def validate_main_character(sender, instance, *args, **kwargs): @receiver(post_delete, sender=Token) -def validate_main_character_token(sender, instance, *args, **kwargs): - if UserProfile.objects.filter(main_character__character_id=instance.character_id).exists(): - logger.debug( - "Token for a main character {0} is being deleted. Ensuring there are valid tokens to refresh.".format( - instance.character_name)) - profile = UserProfile.objects.get(main_character__character_id=instance.character_id) - if not Token.objects.filter(character_id=instance.character_id).filter( - user=profile.user).filter(refresh_token__isnull=False).exists(): - logger.info( - "No remaining tokens to validate {0} ownership of main character {1}. Resetting main character.".format( - profile.user, profile.main_character)) - # clear main character as we can no longer verify ownership - profile.main_character = None - profile.save() +def validate_ownership(sender, instance, *args, **kwargs): + if not Token.objects.filter(character_owner_hash=instance.character_owner_hash).filter(refresh_token__isnull=False).exists(): + logger.info("No remaining tokens to validate ownership of character {0}. Revoking ownership.".format(instance.character_name)) + CharacterOwnership.objects.filter(owner_hash=instance.character_owner_hash).delete() @receiver(pre_save, sender=User) From e47c04a0b0d08656a03f8ab95543e13b60bb65ba Mon Sep 17 00:00:00 2001 From: Adarnof Date: Fri, 20 Apr 2018 13:28:41 -0400 Subject: [PATCH 36/47] Deactivate services when user loses main character. This will prevent issues with service username formatting when access permissions are granted to the guest state. While users without mains cannot activate a service they could still retain an active account and it's possible to schedule a nickname update task which would subsequently error out. Also it seems like a security issue if someone has a service account but their EVE character isn't known. cc8a7a18d23e9beb6e0a498a314e4c3bcae3f700 prevented accessing the services page without a main, now this ensures users don't have an account to manage. --- allianceauth/services/signals.py | 16 +++++++++++++++- allianceauth/services/tests/test_signals.py | 13 +++++++++++++ allianceauth/tests/auth_utils.py | 6 +++--- 3 files changed, 31 insertions(+), 4 deletions(-) diff --git a/allianceauth/services/signals.py b/allianceauth/services/signals.py index 55478592..912a5a4c 100644 --- a/allianceauth/services/signals.py +++ b/allianceauth/services/signals.py @@ -141,7 +141,7 @@ def pre_delete_user(sender, instance, *args, **kwargs): @receiver(pre_save, sender=User) -def pre_save_user(sender, instance, *args, **kwargs): +def disable_services_on_inactive(sender, instance, *args, **kwargs): logger.debug("Received pre_save from %s" % instance) # check if user is being marked active/inactive if not instance.pk: @@ -154,3 +154,17 @@ def pre_save_user(sender, instance, *args, **kwargs): disable_user(instance) except User.DoesNotExist: pass + + +@receiver(pre_save, sender=UserProfile) +def disable_services_on_no_main(sender, instance, *args, **kwargs): + if not instance.pk: + # new model being created + return + try: + old_instance = UserProfile.objects.get(pk=instance.pk) + if old_instance.main_character and not instance.main_character: + logger.info("Disabling services due to loss of main character for user {0}".format(instance.user)) + disable_user(instance.user) + except UserProfile.DoesNotExist: + pass diff --git a/allianceauth/services/tests/test_signals.py b/allianceauth/services/tests/test_signals.py index 83c1313a..04ff148f 100644 --- a/allianceauth/services/tests/test_signals.py +++ b/allianceauth/services/tests/test_signals.py @@ -9,6 +9,7 @@ from allianceauth.authentication.models import State class ServicesSignalsTestCase(TestCase): def setUp(self): self.member = AuthUtils.create_user('auth_member', disconnect_signals=True) + AuthUtils.add_main_character(self.member, 'Test', '1', '2', 'Test Corp', 'TEST') self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True) @mock.patch('allianceauth.services.signals.transaction') @@ -67,6 +68,18 @@ class ServicesSignalsTestCase(TestCase): args, kwargs = disable_user.call_args self.assertEqual(self.member, args[0]) + @mock.patch('allianceauth.services.signals.disable_user') + def test_disable_services_on_loss_of_main_character(self, disable_user): + """ + Test a user set inactive has disable_member called + """ + self.member.profile.main_character = None + self.member.profile.save() # Signal Trigger + + self.assertTrue(disable_user.called) + args, kwargs = disable_user.call_args + self.assertEqual(self.member, args[0]) + @mock.patch('allianceauth.services.signals.transaction') @mock.patch('allianceauth.services.signals.ServicesHook') def test_m2m_changed_group_permissions(self, services_hook, transaction): diff --git a/allianceauth/tests/auth_utils.py b/allianceauth/tests/auth_utils.py index cc27676a..08a10f19 100644 --- a/allianceauth/tests/auth_utils.py +++ b/allianceauth/tests/auth_utils.py @@ -9,7 +9,7 @@ from allianceauth.eveonline.models import EveCharacter from allianceauth.services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions, \ m2m_changed_state_permissions -from allianceauth.services.signals import m2m_changed_user_groups, pre_save_user +from allianceauth.services.signals import m2m_changed_user_groups, disable_services_on_inactive class AuthUtils: @@ -90,7 +90,7 @@ class AuthUtils: m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through) m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through) m2m_changed.disconnect(m2m_changed_state_permissions, sender=State.permissions.through) - pre_save.disconnect(pre_save_user, sender=User) + pre_save.disconnect(disable_services_on_inactive, sender=User) m2m_changed.disconnect(state_member_corporations_changed, sender=State.member_corporations.through) m2m_changed.disconnect(state_member_characters_changed, sender=State.member_characters.through) m2m_changed.disconnect(state_member_alliances_changed, sender=State.member_alliances.through) @@ -102,7 +102,7 @@ class AuthUtils: m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through) m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through) m2m_changed.connect(m2m_changed_state_permissions, sender=State.permissions.through) - pre_save.connect(pre_save_user, sender=User) + pre_save.connect(disable_services_on_inactive, sender=User) m2m_changed.connect(state_member_corporations_changed, sender=State.member_corporations.through) m2m_changed.connect(state_member_characters_changed, sender=State.member_characters.through) m2m_changed.connect(state_member_alliances_changed, sender=State.member_alliances.through) From 98230d0ee33bdd46013a26d7b03d7efbae975a6d Mon Sep 17 00:00:00 2001 From: Adarnof Date: Fri, 20 Apr 2018 14:15:19 -0400 Subject: [PATCH 37/47] Log but don't deal with problems refreshing tokens. --- allianceauth/authentication/tasks.py | 18 +++++--- allianceauth/authentication/tests.py | 66 ++++++++++++++++++---------- setup.py | 2 +- 3 files changed, 57 insertions(+), 29 deletions(-) diff --git a/allianceauth/authentication/tasks.py b/allianceauth/authentication/tasks.py index 61cc49a5..3a9064f1 100644 --- a/allianceauth/authentication/tasks.py +++ b/allianceauth/authentication/tasks.py @@ -1,6 +1,6 @@ import logging -from esi.errors import TokenExpiredError, TokenInvalidError +from esi.errors import TokenExpiredError, TokenInvalidError, IncompleteResponseError from esi.models import Token from celery import shared_task @@ -20,13 +20,19 @@ def check_character_ownership(owner_hash): except (TokenExpiredError, TokenInvalidError): t.delete() continue - - if t.character_owner_hash == old_hash: + except (KeyError, IncompleteResponseError): + # We can't validate the hash hasn't changed but also can't assume it has. Abort for now. + logger.warning("Failed to validate owner hash of {0} due to problems contacting SSO servers.".format( + tokens[0].character_name)) break - else: - logger.info('Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count())) + + if not t.character_owner_hash == old_hash: + logger.info( + 'Character %s has changed ownership. Revoking %s tokens.' % (t.character_name, tokens.count())) tokens.delete() - else: + break + + if not Token.objects.filter(character_owner_hash=owner_hash).exists(): logger.info('No tokens found with owner hash %s. Revoking ownership.' % owner_hash) CharacterOwnership.objects.filter(owner_hash=owner_hash).delete() diff --git a/allianceauth/authentication/tests.py b/allianceauth/authentication/tests.py index fb63f52e..2e235455 100644 --- a/allianceauth/authentication/tests.py +++ b/allianceauth/authentication/tests.py @@ -8,6 +8,7 @@ from .backends import StateBackend from .tasks import check_character_ownership from allianceauth.eveonline.models import EveCharacter, EveCorporationInfo, EveAllianceInfo from esi.models import Token +from esi.errors import IncompleteResponseError from allianceauth.authentication.decorators import main_character_required from django.test.client import RequestFactory from django.http.response import HttpResponse @@ -194,28 +195,6 @@ class CharacterOwnershipTestCase(TestCase): self.user = User.objects.get(pk=self.user.pk) self.assertIsNone(self.user.profile.main_character) - @mock.patch('esi.models.Token.update_token_data') - def test_character_ownership_check(self, update_token_data): - t = Token.objects.create( - user=self.user, - character_id=self.character.character_id, - character_name=self.character.character_name, - character_owner_hash='1', - ) - co = CharacterOwnership.objects.get(owner_hash='1') - check_character_ownership(co.owner_hash) - self.assertTrue(CharacterOwnership.objects.filter(owner_hash='1').exists()) - - t.character_owner_hash = '2' - t.save() - check_character_ownership(co.owner_hash) - self.assertFalse(CharacterOwnership.objects.filter(owner_hash='1').exists()) - - t.delete() - co = CharacterOwnership.objects.create(user=self.user, character=self.character, owner_hash='3') - check_character_ownership(co.owner_hash) - self.assertFalse(CharacterOwnership.objects.filter(owner_hash='3').exists()) - class StateTestCase(TestCase): @classmethod @@ -350,3 +329,46 @@ class StateTestCase(TestCase): self.user.save() self._refresh_user() self.assertEquals(self.user.profile.state, self.member_state) + + +class CharacterOwnershipCheckTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = AuthUtils.create_user('test_user', disconnect_signals=True) + AuthUtils.add_main_character(cls.user, 'Test Character', '1', corp_id='1', alliance_id='1', + corp_name='Test Corp', alliance_name='Test Alliance') + cls.character = EveCharacter.objects.get(character_id='1') + cls.token = Token.objects.create( + user=cls.user, + character_id='1', + character_name='Test', + character_owner_hash='1', + ) + cls.ownership = CharacterOwnership.objects.get(character=cls.character) + + @mock.patch(MODULE_PATH + '.tasks.Token.update_token_data') + def test_no_change_owner_hash(self, update_token_data): + # makes sure the ownership isn't delete if owner hash hasn't changed + check_character_ownership(self.ownership) + self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists()) + + @mock.patch(MODULE_PATH + '.tasks.Token.update_token_data') + def test_unable_to_update_token_data(self, update_token_data): + # makes sure ownerships and tokens aren't hellpurged when there's problems with the SSO servers + update_token_data.side_effect = IncompleteResponseError() + check_character_ownership(self.ownership) + self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists()) + + update_token_data.side_effect = KeyError() + check_character_ownership(self.ownership) + self.assertTrue(CharacterOwnership.objects.filter(user=self.user).filter(character=self.character).exists()) + + @mock.patch(MODULE_PATH + '.tasks.Token.update_token_data') + @mock.patch(MODULE_PATH + '.tasks.Token.delete') + @mock.patch(MODULE_PATH + '.tasks.Token.objects.exists') + @mock.patch(MODULE_PATH + '.tasks.CharacterOwnership.objects.filter') + def test_owner_hash_changed(self, filter, exists, delete, update_token_data): + # makes sure the ownership is revoked when owner hash changes + filter.return_value.exists.return_value = False + check_character_ownership(self.ownership) + self.assertTrue(filter.return_value.delete.called) diff --git a/setup.py b/setup.py index 049a34ca..91198ff7 100644 --- a/setup.py +++ b/setup.py @@ -26,7 +26,7 @@ install_requires = [ 'openfire-restapi', 'sleekxmpp', - 'adarnauth-esi>=1.4,<2.0', + 'adarnauth-esi>=1.4.10,<2.0', ] testing_extras = [ From 5d5cf92a19c8d8cdf6a69393d824d90a385f36c6 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 21 Apr 2018 17:00:18 -0400 Subject: [PATCH 38/47] Remove sudo from docs. Include section on logging DEBUG messages. Cleanup some formatting. --- docs/installation/auth/allianceauth.md | 2 +- docs/installation/auth/gunicorn.md | 4 ++-- docs/installation/auth/upgradev1.md | 7 ++++-- docs/installation/services/discord.md | 12 ++++++++-- docs/installation/services/market.md | 30 ++++++++++++------------ docs/installation/services/mumble.md | 17 ++++++-------- docs/installation/services/openfire.md | 15 ++++-------- docs/installation/services/phpbb3.md | 12 +++++----- docs/installation/services/smf.md | 8 +++---- docs/installation/services/teamspeak3.md | 14 +++++------ docs/maintenance/troubleshooting.md | 14 ++++++----- 11 files changed, 70 insertions(+), 65 deletions(-) diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index 222feff0..2e55817c 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -2,7 +2,7 @@ ```eval_rst .. tip:: - If you are uncomfortable with Linux permissions follow the steps below as the root user. Some commands do not behave the same when run with sudo. + If you are uncomfortable with Linux permissions follow the steps below as the root user. ``` ## Dependencies diff --git a/docs/installation/auth/gunicorn.md b/docs/installation/auth/gunicorn.md index 46b2db37..7e35b305 100644 --- a/docs/installation/auth/gunicorn.md +++ b/docs/installation/auth/gunicorn.md @@ -65,7 +65,7 @@ e.g. `command=/path/to/venv/bin/gunicorn myauth.wsgi` ### Starting via Supervisor -Once you have your configuration all sorted, you will need to reload your supervisor config `sudo service supervisor reload` and then you can start the Gunicorn server via `sudo supervisorctl start aauth-gunicorn` (or whatever you renamed it to). You should see something like the following `aauth-gunicorn: started`. If you get some other message, you'll need to consult the Supervisor log files, usually found in `/var/log/supervisor/`. +Once you have your configuration all sorted, you will need to reload your supervisor config `service supervisor reload` and then you can start the Gunicorn server via `supervisorctl start aauth-gunicorn` (or whatever you renamed it to). You should see something like the following `aauth-gunicorn: started`. If you get some other message, you'll need to consult the Supervisor log files, usually found in `/var/log/supervisor/`. ## Configuring your webserver @@ -76,4 +76,4 @@ Any web server capable of proxy passing should be able to sit in front of Gunico ## Restarting Gunicorn In the past when you made changes you restarted the entire Apache server. This is no longer required. When you update or make configuration changes that ask you to restart Apache, instead you can just restart Gunicorn: -`sudo supervisorctl restart myauth-gunicorn`, or the service name you chose for it. +`supervisorctl restart myauth-gunicorn`, or the service name you chose for it. diff --git a/docs/installation/auth/upgradev1.md b/docs/installation/auth/upgradev1.md index 2a663a93..0e0640ed 100644 --- a/docs/installation/auth/upgradev1.md +++ b/docs/installation/auth/upgradev1.md @@ -60,10 +60,13 @@ If you used member/blue group names other than the standard "Member" and "Blue" Any permissions assigned to these groups will be copied to the state replacing them. Because these groups are no longer managed they pose a security risk and so are deleted at the end of the migration automatically. - +### Run Migrations + +Once you've configured any optional settings it is now safe to run the included migrations. + ## Validating Upgrade -Before starting the Celery workers it's a good idea to validate the states were created and assigned correctly. Any mistakes now will trigger deletion of service accounts: if celery workers aren't running these tasks aren't yet processed. States can be checked through the admin site. +Before starting the Celery workers it's a good idea to validate the states were created and assigned correctly. Any mistakes now will trigger deletion of service accounts: if Celery workers aren't running these tasks aren't yet processed. States can be checked through the admin site. The site (and not Celery) can be started with `supervisorctl start myauth:gunicorn`. Then navigate to the admin site and log in with your v1 username and password. States and User Profiles can be found under the Authentication app. diff --git a/docs/installation/services/discord.md b/docs/installation/services/discord.md index 5f3ad75e..72602741 100644 --- a/docs/installation/services/discord.md +++ b/docs/installation/services/discord.md @@ -21,8 +21,6 @@ In your auth project's settings file, do the following: DISCORD_SYNC_NAMES = False ### Creating a Server -*If you already have a Discord server, skip the creation step, but be sure to retrieve the server ID* - Navigate to the [Discord site](https://discordapp.com/) and register an account, or log in if you have one already. On the left side of the screen you’ll see a circle with a plus sign. This is the button to create a new server. Go ahead and do that, naming it something obvious. @@ -31,6 +29,11 @@ Now retrieve the server ID [following this procedure.](https://support.discordap Update your auth project's settings file, inputting the server ID as `DISCORD_GUILD_ID` +```eval_rst +.. note:: + If you already have a Discord server skip the creation step, but be sure to retrieve the server ID +``` + ### Registering an Application Navigate to the [Discord Developers site.](https://discordapp.com/developers/applications/me) Press the plus sign to create a new application. @@ -66,3 +69,8 @@ If you want users to have their Discord nickname changed to their in-game charac ## Managing Roles Once users link their accounts you’ll notice Roles get populated on Discord. These are the equivalent to Groups on every other service. The default permissions should be enough for members to use text and audio communications. Add more permissions to the roles as desired through the server management window. + +## Troubleshooting + +### "Unknown Error" on Discord site when activating service +This indicates your callback URL doesn't match. Ensure the `DISCORD_CALLBACK_URL` setting exactly matches the URL entered on the Discord developers site. This includes http(s), trailing slash, etc. \ No newline at end of file diff --git a/docs/installation/services/market.md b/docs/installation/services/market.md index 0a478b66..7c919d97 100644 --- a/docs/installation/services/market.md +++ b/docs/installation/services/market.md @@ -30,7 +30,7 @@ Alliance Market needs a database. Create one in MySQL/MariaDB. Default name is ` Install required packages to clone the repository: - sudo apt-get install mercurial meld + apt-get install mercurial meld Change to the web folder: @@ -38,18 +38,18 @@ Change to the web folder: Now clone the repository - sudo hg clone https://bitbucket.org/krojew/evernus-alliance-market + hg clone https://bitbucket.org/krojew/evernus-alliance-market Make cache and log directories - sudo mkdir evernus-alliance-market/app/cache - sudo mkdir evernus-alliance-market/app/logs - sudo chmod -R 777 evernus-alliance-market/app/cache - sudo chmod -R 777 evernus-alliance-market/app/logs + mkdir evernus-alliance-market/app/cache + mkdir evernus-alliance-market/app/logs + chmod -R 777 evernus-alliance-market/app/cache + chmod -R 777 evernus-alliance-market/app/logs Change ownership to apache - sudo chown -R www-data:www-data evernus-alliance-market + chown -R www-data:www-data evernus-alliance-market Enter directory @@ -61,7 +61,7 @@ Set environment variable Copy configuration - sudo cp app/config/parameters.yml.dist app/config/parameters.yml + cp app/config/parameters.yml.dist app/config/parameters.yml Edit, changing the following: - `database_name` to `alliance_market` @@ -79,25 +79,25 @@ Install composer [as per these instructions.](https://getcomposer.org/download/) Update dependencies. - sudo php composer.phar update --optimize-autoloader + php composer.phar update --optimize-autoloader Prepare the cache: - sudo php app/console cache:clear --env=prod --no-debug + php app/console cache:clear --env=prod --no-debug Dump assets: - sudo php app/console assetic:dump --env=prod --no-debug + php app/console assetic:dump --env=prod --no-debug Create DB entries - sudo php app/console doctrine:schema:update --force + php app/console doctrine:schema:update --force Install SDE: - sudo php app/console evernus:update:sde + php app/console evernus:update:sde Configure your web server to serve alliance market. @@ -150,11 +150,11 @@ A minimal Nginx config might look like: Once again, set cache permissions: - sudo chown -R www-data:www-data app/ + chown -R www-data:www-data app/ Add a user account through auth, then make it a superuser: - sudo php app/console fos:user:promote your_username --super + php app/console fos:user:promote your_username --super Now edit your auth project's settings file and fill in the web URL to your market as well as the database details. diff --git a/docs/installation/services/mumble.md b/docs/installation/services/mumble.md index f628d42a..87b6cd1e 100644 --- a/docs/installation/services/mumble.md +++ b/docs/installation/services/mumble.md @@ -15,21 +15,21 @@ Mumble is a free voice chat server. While not as flashy as TeamSpeak, it has all ## Dependencies The mumble server package can be retrieved from a repository we need to add, mumble/release. - sudo apt-add-repository ppa:mumble/release - sudo apt-get update + apt-add-repository ppa:mumble/release + apt-get update Now two packages need to be installed: - sudo apt-get install python-software-properties mumble-server + apt-get install python-software-properties mumble-server -Download the appropriate authenticator release from https://github.com/allianceauth/mumble-authenticator and install the python dependencies for it: +Download the appropriate authenticator release from [the authenticator repository](https://github.com/allianceauth/mumble-authenticator) and install the python dependencies for it: pip install -r requirements.txt ## Configuring Mumble Mumble ships with a configuration file that needs customization. By default it’s located at /etc/mumble-server.ini. Open it with your favourite text editor: - sudo nano /etc/mumble-server.ini + nano /etc/mumble-server.ini REQUIRED: To enable the ICE authenticator, edit the following: @@ -50,13 +50,13 @@ Save and close the file (control + O, control + X). To get Mumble superuser account credentials, run the following: - sudo dpkg-reconfigure mumble-server + dpkg-reconfigure mumble-server Set the password to something you’ll remember and write it down. This is needed to manage ACLs. Now restart the server to see the changes reflected. - sudo service mumble-server restart + service mumble-server restart That’s it! Your server is ready to be connected to at example.com:64738 @@ -84,9 +84,6 @@ The authenticator needs to be running 24/7 to validate users on Mumble. You shou Note that groups will only be created on Mumble automatically when a user joins who is in the group. -## Making and Managing Channels -ACL is really above the scope of this guide. Once Alliance Auth creates your groups, go ahead and follow one of the wonderful web guides available on how to set up channel ACL properly. - ## Prepare Auth In your project's settings file, set `MUMBLE_URL` to the public address of your mumble server. Do not include any leading `http://` or `mumble://`. diff --git a/docs/installation/services/openfire.md b/docs/installation/services/openfire.md index 2190862b..e9c19577 100644 --- a/docs/installation/services/openfire.md +++ b/docs/installation/services/openfire.md @@ -17,21 +17,16 @@ BROADCAST_USER_PASSWORD = "" BROADCAST_SERVICE_NAME = "broadcast" -## Overview -Openfire is a Java-based XMPP server (Jabber). - ## Dependencies -One additional package is required - openjdk8 +Openfire require a Java 8 runtime environment. Ubuntu: - sudo add-apt-repository ppa:webupd8team/java -y - sudo apt-get update - sudo apt-get install oracle-java8-installer + apt-get install openjdk-8-jdk CentOS: - sudo yum -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel + yum -y install java-1.8.0-openjdk java-1.8.0-openjdk-devel ## Setup ### Download Installer @@ -51,11 +46,11 @@ Now install from the package. Replace the filename with your filename (the last Ubuntu: - sudo dpkg -i openfire_4.2.3_all.deb + dpkg -i openfire_4.2.3_all.deb CentOS: - sudo yum install -y openfire-4.2.3-1.noarch.rpm + yum install -y openfire-4.2.3-1.noarch.rpm ### Create Database Performance is best when working from a SQL database. If you installed MySQL or MariaDB alongside your auth project, go ahead and create a database for Openfire: diff --git a/docs/installation/services/phpbb3.md b/docs/installation/services/phpbb3.md index d31167cd..ab4abf9d 100644 --- a/docs/installation/services/phpbb3.md +++ b/docs/installation/services/phpbb3.md @@ -34,8 +34,8 @@ Create a database to install phpBB3 in. Edit your auth project's settings file and fill out the `DATABASES['phpbb3']` part. -### Download phpbb3 -phpBB is available as a zip from their website. Navigate to the website’s [downloads section](https://www.phpbb.com/downloads/) using your PC browser and copy the URL for the latest version zip. Depending on your browser you may have a Copy Link or similar option in your right click menu. +### Download phpBB3 +phpBB3 is available as a zip from their website. Navigate to the website’s [downloads section](https://www.phpbb.com/downloads/) using your PC browser and copy the URL for the latest version zip. In the console, navigate to your user’s home directory: `cd ~` @@ -49,12 +49,12 @@ This needs to be unpackaged. Unzip it, replacing the file name with that of the Now we need to move this to our web directory. Usually `/var/www/forums`. - sudo mv phpBB3 /var/www/forums + mv phpBB3 /var/www/forums The web server needs read/write permission to this folder -Apache: `sudo chown -R www-data:www-data /var/www/forums` -Nginx: `sudo chown -R nginx:nginx /var/www/forums` +Apache: `chown -R www-data:www-data /var/www/forums` +Nginx: `chown -R nginx:nginx /var/www/forums` ```eval_rst .. tip:: @@ -133,7 +133,7 @@ phpBB will then write its own config file. ### Open the Forums Before users can see the forums, we need to remove the install directory - sudo rm -rf /var/www/forums/install + rm -rf /var/www/forums/install ### Enabling Avatars AllianceAuth sets user avatars to their character portrait when the account is created or password reset. We need to allow external URLs for avatars for them to behave properly. Navigate to the admin control panel for phpbb3, and under the `General` tab, along the left navigation bar beneath `Board Configuration`, select `Avatar Settings`. Set `Enable Remote Avatars` to `Yes` and then `Submit`. diff --git a/docs/installation/services/smf.md b/docs/installation/services/smf.md index 6a0d34ce..72d222b5 100644 --- a/docs/installation/services/smf.md +++ b/docs/installation/services/smf.md @@ -38,12 +38,12 @@ This needs to be unpackaged. Unzip it, replacing the file name with that of the Now we need to move this to our web directory. Usually `/var/www/forums`. - sudo mv smf /var/www/forums + mv smf /var/www/forums The web server needs read/write permission to this folder -Apache: `sudo chown -R www-data:www-data /var/www/forums` -Nginx: `sudo chown -R nginx:nginx /var/www/forums` +Apache: `chown -R www-data:www-data /var/www/forums` +Nginx: `chown -R nginx:nginx /var/www/forums` ```eval_rst .. tip:: @@ -80,7 +80,7 @@ A minimal Nginx config might look like: listen 80; server_name forums.example.com; root /var/www/forums; - index app.php; + index index.php; access_log /var/logs/forums.access.log; location ~ \.php$ { diff --git a/docs/installation/services/teamspeak3.md b/docs/installation/services/teamspeak3.md index 91fbeeae..289a65b9 100644 --- a/docs/installation/services/teamspeak3.md +++ b/docs/installation/services/teamspeak3.md @@ -41,26 +41,26 @@ Now we need to extract the file. ### Create User TeamSpeak needs its own user. - sudo adduser --disabled-login teamspeak + adduser --disabled-login teamspeak ### Install Binary Now we move the server binary somewhere more accessible and change its ownership to the new user. - sudo mv teamspeak3-server_linux_amd64 /usr/local/teamspeak - sudo chown -R teamspeak:teamspeak /usr/local/teamspeak + mv teamspeak3-server_linux_amd64 /usr/local/teamspeak + chown -R teamspeak:teamspeak /usr/local/teamspeak ### Startup Now we generate a startup script so TeamSpeak comes up with the server. - sudo ln -s /usr/local/teamspeak/ts3server_startscript.sh /etc/init.d/teamspeak - sudo update-rc.d teamspeak defaults + ln -s /usr/local/teamspeak/ts3server_startscript.sh /etc/init.d/teamspeak + update-rc.d teamspeak defaults Finally we start the server. - sudo service teamspeak start + service teamspeak start ### Update Settings -The console will spit out a block of text. If it does not appear, it can be found with `sudo service teamspeak status`. **SAVE THIS**. +The console will spit out a block of text. If it does not appear, it can be found with `service teamspeak status`. **SAVE THIS**. If you plan on claiming the ServerAdmin token, do so with a different TeamSpeak client profile than the one used for your auth account, or you will lose your admin status. diff --git a/docs/maintenance/troubleshooting.md b/docs/maintenance/troubleshooting.md index 9737e991..def222da 100644 --- a/docs/maintenance/troubleshooting.md +++ b/docs/maintenance/troubleshooting.md @@ -2,18 +2,20 @@ ## Something broken? Stuck on an issue? Can't get it set up? -Start by checking the [issues](https://github.com/allianceauth/allianceauth/issues?utf8=%E2%9C%93&q=is%3Aissue) - especially closed ones. +Start by checking the [issues](https://github.com/allianceauth/allianceauth/issues?q=is%3Aissue) - especially closed ones. No answer? - open an [issue](https://github.com/allianceauth/allianceauth/issues) - harass us on [gitter](https://gitter.im/R4stl1n/allianceauth) + +## Logging + +In its default configuration your auth project logs INFO and above messages to myauth/log/allianceauth.log. If you're encountering issues it's a good idea to view DEBUG messages as these greatly assist the troubleshooting process. These are printed to the console with manually starting the webserver via `python manage.py runserver`. + +To record DEBUG messages in the log file, alter a setting in your auth project's settings file: `LOGGING['handlers']['log_file']['level'] = 'DEBUG'`. After restarting gunicorn and celery your log file will record all logging messages. ## Common Problems -### `pip install -r requirements.txt` is failing - -It's probably a permissions issue. Ensure your current user can write to the virtual environment folder. That, or you're missing a dependency of some kind which will be indicated in the error message. - ### I'm getting an error 500 trying to connect to the website on a new install *Great.* Error 500 is the generic message given by your web server when *anything* breaks. The actual error message is hidden in one of your auth project's log files. Read them to identify it. @@ -43,7 +45,7 @@ This usually indicates an issue with your email settings. Ensure these are corre ### No images are available to users accessing the website -This is due to a permissions mismatch, check the setup guide for your web server. Additionally ensure the user who owns /var/www/myauth/static is the same user as running your webserver, as this can be non-standard. +This is likely due to a permissions mismatch. Check the setup guide for your web server. Additionally ensure the user who owns `/var/www/myauth/static` is the same user as running your webserver, as this can be non-standard. ### Unable to execute 'gunicorn myauth.wsgi' or ImportError: No module named 'myauth.wsgi' From cd382005067270c9b7c67c647ed2ba01ddd12bd9 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 21 Apr 2018 19:49:46 -0400 Subject: [PATCH 39/47] Section for adding and removing apps. People know how to add, but tend not to migrate to zero when removing leading to integrity errors. --- docs/maintenance/project.md | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/docs/maintenance/project.md b/docs/maintenance/project.md index 79214a92..832975a6 100644 --- a/docs/maintenance/project.md +++ b/docs/maintenance/project.md @@ -86,3 +86,12 @@ urlpatterns = [ url(r'', include(allianceauth.urls)), ] ``` + +## Adding and Removing Apps + +Your auth project is just a regular Django project - you can add in [other Django apps](https://djangopackages.org/) as desired. Most come with dedicated setup guides, but in general: + - add `'appname',` to your `INSTALLED_APPS` setting + - run `python manage.py migrate` + - run `python manage.py collectstatic` + +If you ever want to remove an app, you should first clear it from the database to avoid dangling foreign keys: `python manage.py migrate appname zero`. Then you can remove it from your auth project's `INSTALLED_APPS` list. \ No newline at end of file From cbe67e9ebcf57443fde6427e7e2380d3f70e2092 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 21 Apr 2018 20:28:27 -0400 Subject: [PATCH 40/47] Command to reset unverifiable main characters. Include section in upgrade docs to run this command and the service account validation one. --- .../authentication/management/__init__.py | 0 .../management/commands/__init__.py | 0 .../management/commands/checkmains.py | 20 +++++++++++++ allianceauth/authentication/tests.py | 30 ++++++++++++++++++- docs/installation/auth/upgradev1.md | 6 ++++ 5 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 allianceauth/authentication/management/__init__.py create mode 100644 allianceauth/authentication/management/commands/__init__.py create mode 100644 allianceauth/authentication/management/commands/checkmains.py diff --git a/allianceauth/authentication/management/__init__.py b/allianceauth/authentication/management/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/authentication/management/commands/__init__.py b/allianceauth/authentication/management/commands/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/allianceauth/authentication/management/commands/checkmains.py b/allianceauth/authentication/management/commands/checkmains.py new file mode 100644 index 00000000..6c4b7fb2 --- /dev/null +++ b/allianceauth/authentication/management/commands/checkmains.py @@ -0,0 +1,20 @@ +from django.core.management.base import BaseCommand +from allianceauth.authentication.models import UserProfile + + +class Command(BaseCommand): + help = 'Ensures all main characters have an active ownership' + + def handle(self, *args, **options): + profiles = UserProfile.objects.filter(main_character__isnull=False).filter( + main_character__character_ownership__isnull=True) + if profiles.exists(): + for profile in profiles: + self.stdout.write(self.style.ERROR( + '{0} does not have an ownership. Resetting user {1} main character.'.format(profile.main_character, + profile.user))) + profile.main_character = None + profile.save() + self.stdout.write(self.style.WARNING('Reset {0} main characters.'.format(profiles.count()))) + else: + self.stdout.write(self.style.SUCCESS('All main characters have active ownership.')) diff --git a/allianceauth/authentication/tests.py b/allianceauth/authentication/tests.py index 2e235455..fc558f41 100644 --- a/allianceauth/authentication/tests.py +++ b/allianceauth/authentication/tests.py @@ -1,5 +1,5 @@ from unittest import mock - +from io import StringIO from django.test import TestCase from django.contrib.auth.models import User from allianceauth.tests.auth_utils import AuthUtils @@ -15,6 +15,7 @@ from django.http.response import HttpResponse from django.contrib.auth.models import AnonymousUser from django.conf import settings from django.shortcuts import reverse +from django.core.management import call_command from urllib import parse MODULE_PATH = 'allianceauth.authentication' @@ -372,3 +373,30 @@ class CharacterOwnershipCheckTestCase(TestCase): filter.return_value.exists.return_value = False check_character_ownership(self.ownership) self.assertTrue(filter.return_value.delete.called) + + +class ManagementCommandTestCase(TestCase): + @classmethod + def setUpTestData(cls): + cls.user = AuthUtils.create_user('test user', disconnect_signals=True) + AuthUtils.add_main_character(cls.user, 'test character', '1', '2', 'test corp', 'test') + character = UserProfile.objects.get(user=cls.user).main_character + CharacterOwnership.objects.create(user=cls.user, character=character, owner_hash='test') + + def setUp(self): + self.stdout = StringIO() + + def test_ownership(self): + call_command('checkmains', stdout=self.stdout) + self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count()) + self.assertNotIn(self.user.username, self.stdout.getvalue()) + self.assertIn('All main characters', self.stdout.getvalue()) + + def test_no_ownership(self): + user = AuthUtils.create_user('v1 user', disconnect_signals=True) + AuthUtils.add_main_character(user, 'v1 character', '10', '20', 'test corp', 'test') + self.assertFalse(UserProfile.objects.filter(main_character__isnull=True).count()) + + call_command('checkmains', stdout=self.stdout) + self.assertEqual(UserProfile.objects.filter(main_character__isnull=True).count(), 1) + self.assertIn(user.username, self.stdout.getvalue()) diff --git a/docs/installation/auth/upgradev1.md b/docs/installation/auth/upgradev1.md index 0e0640ed..ecb720b4 100644 --- a/docs/installation/auth/upgradev1.md +++ b/docs/installation/auth/upgradev1.md @@ -74,6 +74,12 @@ Once you have confirmed states are correctly configured (or adjusted them as nee It should now be safe to bring workers online (`supervisorctl start myauth:`) and invite users to use the site. +## Securing User Accounts + +After migration users will have main characters without a method to check for character sales. After a brief period to allow your users to log in (and provide a refreshable token to use), it's a good idea to clear main characters of users who haven't yet logged in. This can be achieved with an included command: `python manage.py checkmains` + +A similar process can be used to ensure users who may have lost service permissions during the migration do not have active service accounts. This is done using `python manage.py validate_service_accounts`. + ## Help If something goes wrong during the migration reach out for help on [Gitter](https://gitter.im/R4stl1n/allianceauth) or open an [issue](https://github.com/allianceauth/allianceauth/issues). \ No newline at end of file From 507eda8a7df6ea5f71debc039e3eaf6e4cdef0a1 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sat, 21 Apr 2018 20:44:15 -0400 Subject: [PATCH 41/47] Version bump to 2.0 --- README.md | 1 + allianceauth/__init__.py | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 1d9c9da1..4d34c85c 100755 --- a/README.md +++ b/README.md @@ -23,6 +23,7 @@ Beta Testers / Bug Fixers: - [ghoti](https://github.com/ghoti/) - [mmolitor87](https://github.com/mmolitor87/) + - [TargetZ3R0](https://github.com/TargetZ3R0) - [kaezon](https://github.com/kaezon/) - [orbitroom](https://github.com/orbitroom/) - [tehfiend](https://github.com/tehfiend/) diff --git a/allianceauth/__init__.py b/allianceauth/__init__.py index 2031c58e..d76d5508 100644 --- a/allianceauth/__init__.py +++ b/allianceauth/__init__.py @@ -1,7 +1,7 @@ # This will make sure the app is always imported when # Django starts so that shared_task will use this app. -__version__ = '2.0b3' +__version__ = '2.0.0' NAME = 'Alliance Auth v%s' % __version__ default_app_config = 'allianceauth.apps.AllianceAuthConfig' From 37bed989f13c38f558f13cb27d1016cc524c6906 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Sun, 22 Apr 2018 12:50:22 -0400 Subject: [PATCH 42/47] Requires mariadb-shared for mysqlclient on centos. Thanks @rlayne --- docs/installation/auth/allianceauth.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/installation/auth/allianceauth.md b/docs/installation/auth/allianceauth.md index 2e55817c..e477b4ec 100644 --- a/docs/installation/auth/allianceauth.md +++ b/docs/installation/auth/allianceauth.md @@ -39,7 +39,7 @@ Ubuntu: CentOS: - yum install mariadb-server mariadb-devel mariadb + yum install mariadb-server mariadb-devel mariadb-shared mariadb ```eval_rst .. note:: From b4d33e5dfc179f36613ad18d158ec6e997e5fc65 Mon Sep 17 00:00:00 2001 From: randomic Date: Tue, 24 Apr 2018 16:53:13 +0100 Subject: [PATCH 43/47] Fix retry logic being suppressed by try block (#1035) --- .../services/modules/discord/manager.py | 26 ++++++++----------- 1 file changed, 11 insertions(+), 15 deletions(-) diff --git a/allianceauth/services/modules/discord/manager.py b/allianceauth/services/modules/discord/manager.py index 7f28e6c7..6fe7a7d4 100644 --- a/allianceauth/services/modules/discord/manager.py +++ b/allianceauth/services/modules/discord/manager.py @@ -219,22 +219,18 @@ class DiscordOAuthManager: @staticmethod @api_backoff def update_nickname(user_id, nickname): - try: - nickname = DiscordOAuthManager._sanitize_name(nickname) - custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN} - data = {'nick': nickname} - path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id) - r = requests.patch(path, headers=custom_headers, json=data) - logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % ( - r.status_code, user_id, nickname)) - if r.status_code == 404: - logger.warn("Discord user ID %s could not be found in server." % user_id) - return True - r.raise_for_status() + nickname = DiscordOAuthManager._sanitize_name(nickname) + custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN} + data = {'nick': nickname} + path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id) + r = requests.patch(path, headers=custom_headers, json=data) + logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % ( + r.status_code, user_id, nickname)) + if r.status_code == 404: + logger.warn("Discord user ID %s could not be found in server." % user_id) return True - except: - logger.exception("Failed to set nickname for Discord user ID %s (%s)" % (user_id, nickname)) - return False + r.raise_for_status() + return True @staticmethod def delete_user(user_id): From ca10fbcde5510b9995003c13a53eb4c6b978414b Mon Sep 17 00:00:00 2001 From: Adarnof Date: Wed, 25 Apr 2018 17:17:40 -0400 Subject: [PATCH 44/47] Translate Member/Blue to custom state names. Closes #1037 --- .../migrations/0015_user_profiles.py | 27 +++++++++++++++---- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/allianceauth/authentication/migrations/0015_user_profiles.py b/allianceauth/authentication/migrations/0015_user_profiles.py index 592ba0e8..9a37cf82 100644 --- a/allianceauth/authentication/migrations/0015_user_profiles.py +++ b/allianceauth/authentication/migrations/0015_user_profiles.py @@ -133,15 +133,24 @@ def create_profiles(apps, schema_editor): auth['n'] == 1 and EveCharacter.objects.filter(character_id=auth['main_char_id']).exists()] auths = AuthServicesInfo.objects.filter(main_char_id__in=unique_mains).select_related('user') + + blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue') + member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member') + + states = { + 'Member': State.objects.get(name=member_state_name), + 'Blue': State.objects.get(name=blue_state_name), + } + guest_state = State.objects.get(name='Guest') + for auth in auths: # carry states and mains forward - state = State.objects.get(name=auth.state if auth.state else 'Guest') + state = states.get(auth.state, guest_state) char = EveCharacter.objects.get(character_id=auth.main_char_id) UserProfile.objects.create(user=auth.user, state=state, main_character=char) for auth in AuthServicesInfo.objects.exclude(main_char_id__in=unique_mains).select_related('user'): # prepare empty profiles - state = State.objects.get(name='Guest') - UserProfile.objects.create(user=auth.user, state=state) + UserProfile.objects.create(user=auth.user, state=guest_state) def recreate_authservicesinfo(apps, schema_editor): @@ -149,6 +158,14 @@ def recreate_authservicesinfo(apps, schema_editor): UserProfile = apps.get_model('authentication', 'UserProfile') User = apps.get_model('auth', 'User') + blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue') + member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member') + + states = { + member_state_name: 'Member', + blue_state_name: 'Blue', + } + # recreate all missing AuthServicesInfo models AuthServicesInfo.objects.bulk_create([AuthServicesInfo(user_id=u.pk) for u in User.objects.all()]) @@ -159,8 +176,8 @@ def recreate_authservicesinfo(apps, schema_editor): # repopulate states we understand for profile in UserProfile.objects.exclude(state__name='Guest').filter( - state__name__in=['Member', 'Blue']).select_related('user', 'state'): - AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': profile.state.name}) + state__name__in=[member_state_name, blue_state_name]).select_related('user', 'state'): + AuthServicesInfo.objects.update_or_create(user=profile.user, defaults={'state': states[profile.state.name]}) def disable_passwords(apps, schema_editor): From 53a9d72c4afeec518e93e4a474ac0e8756ee939a Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 30 Apr 2018 17:24:31 -0400 Subject: [PATCH 45/47] Correct reversing states back to groups. --- .../authentication/migrations/0015_user_profiles.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/allianceauth/authentication/migrations/0015_user_profiles.py b/allianceauth/authentication/migrations/0015_user_profiles.py index 9a37cf82..983871b5 100644 --- a/allianceauth/authentication/migrations/0015_user_profiles.py +++ b/allianceauth/authentication/migrations/0015_user_profiles.py @@ -43,7 +43,7 @@ def create_member_group(apps, schema_editor): member_state_name = getattr(settings, 'DEFAULT_AUTH_GROUP', 'Member') try: - g = Group.objects.get(name=member_state_name) + g, _ = Group.objects.get_or_create(name=member_state_name) # move permissions back state = State.objects.get(name=member_state_name) [g.permissions.add(p.pk) for p in state.permissions.all()] @@ -51,7 +51,7 @@ def create_member_group(apps, schema_editor): # move users back for profile in state.userprofile_set.all().select_related('user'): profile.user.groups.add(g.pk) - except (Group.DoesNotExist, State.DoesNotExist): + except State.DoesNotExist: pass @@ -67,7 +67,7 @@ def create_blue_state(apps, schema_editor): # move group permissions to state g = Group.objects.get(name=blue_state_name) [s.permissions.add(p.pk) for p in g.permissions.all()] - g.permissions.clear() + g.delete() except Group.DoesNotExist: pass @@ -84,7 +84,7 @@ def create_blue_group(apps, schema_editor): blue_state_name = getattr(settings, 'DEFAULT_BLUE_GROUP', 'Blue') try: - g = Group.objects.get(name=blue_state_name) + g, _ = Group.objects.get_or_create(name=blue_state_name) # move permissions back state = State.objects.get(name=blue_state_name) [g.permissions.add(p.pk) for p in state.permissions.all()] @@ -92,7 +92,7 @@ def create_blue_group(apps, schema_editor): # move users back for profile in state.userprofile_set.all().select_related('user'): profile.user.groups.add(g.pk) - except (Group.DoesNotExist, State.DoesNotExist): + except State.DoesNotExist: pass From f6b1b7b6bb444b907c3b6b53bf707858b76fddb0 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Mon, 30 Apr 2018 17:29:06 -0400 Subject: [PATCH 46/47] Do not check mains when user has no profile. This can occur when a user is being deleted: Django deletes the UserProfile, followed by the CharacterOwnerships which triggers the main check. As the user doesn't have a profile it explodes. Thanks @Slevinator --- allianceauth/__init__.py | 2 +- allianceauth/authentication/signals.py | 16 ++++++++++------ 2 files changed, 11 insertions(+), 7 deletions(-) diff --git a/allianceauth/__init__.py b/allianceauth/__init__.py index d76d5508..8109e93c 100644 --- a/allianceauth/__init__.py +++ b/allianceauth/__init__.py @@ -1,7 +1,7 @@ # This will make sure the app is always imported when # Django starts so that shared_task will use this app. -__version__ = '2.0.0' +__version__ = '2.0.1' NAME = 'Alliance Auth v%s' % __version__ default_app_config = 'allianceauth.apps.AllianceAuthConfig' diff --git a/allianceauth/authentication/signals.py b/allianceauth/authentication/signals.py index bd062b56..6f90cabc 100644 --- a/allianceauth/authentication/signals.py +++ b/allianceauth/authentication/signals.py @@ -103,12 +103,16 @@ def record_character_ownership(sender, instance, created, *args, **kwargs): @receiver(pre_delete, sender=CharacterOwnership) def validate_main_character(sender, instance, *args, **kwargs): - if instance.user.profile.main_character == instance.character: - logger.info("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format( - instance.character, instance.user)) - # clear main character as user no longer owns them - instance.user.profile.main_character = None - instance.user.profile.save() + try: + if instance.user.profile.main_character == instance.character: + logger.info("Ownership of a main character {0} has been revoked. Resetting {1} main character.".format( + instance.character, instance.user)) + # clear main character as user no longer owns them + instance.user.profile.main_character = None + instance.user.profile.save() + except UserProfile.DoesNotExist: + # a user is being deleted + pass @receiver(post_delete, sender=Token) From 7212a7a3285e7b17ae180c6ddac4a18f6332dc49 Mon Sep 17 00:00:00 2001 From: Adarnof Date: Tue, 1 May 2018 16:40:37 -0400 Subject: [PATCH 47/47] Example supervisor config for authenticator. Ensure ICE is active in config. --- docs/installation/services/mumble.md | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/docs/installation/services/mumble.md b/docs/installation/services/mumble.md index 87b6cd1e..b114c550 100644 --- a/docs/installation/services/mumble.md +++ b/docs/installation/services/mumble.md @@ -34,6 +34,7 @@ Mumble ships with a configuration file that needs customization. By default it REQUIRED: To enable the ICE authenticator, edit the following: - `icesecretwrite=MY_CLEVER_PASSWORD`, obviously choosing a secure password + - ensure the line containing `Ice="tcp -h 127.0.0.1 -p 6502"` is uncommented By default mumble operates on SQLite which is fine, but slower than a dedicated MySQL server. To customize the database, edit the following: @@ -46,7 +47,7 @@ By default mumble operates on SQLite which is fine, but slower than a dedicated To name your root channel, uncomment and set `registerName=` to whatever cool name you want -Save and close the file (control + O, control + X). +Save and close the file. To get Mumble superuser account credentials, run the following: @@ -80,7 +81,21 @@ Test your configuration by starting it: `python authenticator.py` ## Running the Authenticator -The authenticator needs to be running 24/7 to validate users on Mumble. You should check the [supervisor docs](../auth/supervisor.md) on how to achieve this. +The authenticator needs to be running 24/7 to validate users on Mumble. This can be achieved by adding a section to your auth project's supervisor config file like the following example: + +``` +[program:authenticator] +command=/path/to/venv/bin/python authenticator.py +directory=/path/to/authenticator/directory/ +user=allianceserver +stdout_logfile=/path/to/authenticator/directory/authenticator.log +stderr_logfile=/path/to/authenticator/directory/authenticator.log +autostart=true +autorestart=true +startsecs=10 +priority=998 +``` + Note that groups will only be created on Mumble automatically when a user joins who is in the group.