Compare commits
122 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4bdead5ef2 | ||
|
|
8987cf2199 | ||
|
|
27c9b09116 | ||
|
|
0ac0f71fef | ||
|
|
3f454743a9 | ||
|
|
c2f12eed26 | ||
|
|
1b1b692ac0 | ||
|
|
dc8ed2d510 | ||
|
|
049c1c66aa | ||
|
|
8028660a8f | ||
|
|
e6532025f8 | ||
|
|
3361d36bbf | ||
|
|
2ab45b1019 | ||
|
|
c5b55283d1 | ||
|
|
d937d5b5d4 | ||
|
|
b41521bcb5 | ||
|
|
882cafb4ba | ||
|
|
5db340c64a | ||
|
|
e76b0789f3 | ||
|
|
d2e32d3da3 | ||
|
|
12cfc552da | ||
|
|
dc10245158 | ||
|
|
1b0c3c3bfc | ||
|
|
aec013b93c | ||
|
|
4556a0e740 | ||
|
|
aad3bd6f57 | ||
|
|
17dd7c04c7 | ||
|
|
372e582c6e | ||
|
|
5a93128f4f | ||
|
|
901dd5033a | ||
|
|
d8043ff735 | ||
|
|
bb3e7a0449 | ||
|
|
806962cda5 | ||
|
|
2cd43280e2 | ||
|
|
6c94640552 | ||
|
|
250c376abb | ||
|
|
fb22aaf731 | ||
|
|
9897c0bbba | ||
|
|
7d0aa2b5ec | ||
|
|
27628dc70b | ||
|
|
ecb74e67b0 | ||
|
|
de47e94870 | ||
|
|
9238ac97cf | ||
|
|
2e274d3baf | ||
|
|
c6118beddf | ||
|
|
e6e1339d71 | ||
|
|
693016e171 | ||
|
|
3e09f2179f | ||
|
|
3a1d0d0335 | ||
|
|
7c14aede26 | ||
|
|
308dc9191f | ||
|
|
1a958384c3 | ||
|
|
078ec785e4 | ||
|
|
97c725f58f | ||
|
|
ed52f4f411 | ||
|
|
4c1c33fa23 | ||
|
|
3d15cb9c57 | ||
|
|
e3e84158f8 | ||
|
|
2d6c641648 | ||
|
|
5cec2f834b | ||
|
|
c0b1523f39 | ||
|
|
f6ea9e0236 | ||
|
|
fd05eff5d4 | ||
|
|
dfd416c38d | ||
|
|
3af1a6a1fb | ||
|
|
7ce47547ba | ||
|
|
fe9da4d5de | ||
|
|
a6c48f8d71 | ||
|
|
a33c8c14ee | ||
|
|
58e121d10d | ||
|
|
8c82897a92 | ||
|
|
d7291f83c3 | ||
|
|
914c204a40 | ||
|
|
918ecf812c | ||
|
|
b636262e0c | ||
|
|
489b9a601d | ||
|
|
ff1c2030ca | ||
|
|
1a749bf712 | ||
|
|
3f8b7f7fc1 | ||
|
|
0902e250d3 | ||
|
|
c0885fa7c5 | ||
|
|
038f948aab | ||
|
|
d7a4d06120 | ||
|
|
13204a7e91 | ||
|
|
3d645867bb | ||
|
|
aa0148f23b | ||
|
|
ed32925525 | ||
|
|
4f7c7ca1c6 | ||
|
|
35ceb53936 | ||
|
|
c4c6d13d36 | ||
|
|
44256c3408 | ||
|
|
2c68f485e2 | ||
|
|
11d52d476c | ||
|
|
1066e6ac98 | ||
|
|
5738b015c3 | ||
|
|
88534837e2 | ||
|
|
026b4c272b | ||
|
|
294cd6b781 | ||
|
|
0292ad07ad | ||
|
|
f5cb6a3fb7 | ||
|
|
0b57e027f7 | ||
|
|
3d50f977d9 | ||
|
|
0abb160886 | ||
|
|
ff3bb9743f | ||
|
|
cf81f59fa6 | ||
|
|
d186088a8f | ||
|
|
2106e492e3 | ||
|
|
badc5a1f7f | ||
|
|
cced434a99 | ||
|
|
6c4e8ec2d1 | ||
|
|
5bbaef4476 | ||
|
|
6208071537 | ||
|
|
df02982983 | ||
|
|
eaa9d1930c | ||
|
|
24bc9d4b7f | ||
|
|
73641a7a1e | ||
|
|
485c0fc373 | ||
|
|
55cbbadc2b | ||
|
|
b1dafeda8d | ||
|
|
380069627a | ||
|
|
c3390b089e | ||
|
|
c9d9b0bf07 |
32
.coveragerc
Normal file
@@ -0,0 +1,32 @@
|
||||
[run]
|
||||
branch = True
|
||||
source =
|
||||
alliance_auth
|
||||
authentication
|
||||
corputils
|
||||
eveonline
|
||||
fleetactivitytracking
|
||||
fleetup
|
||||
groupmanagement
|
||||
hrapplications
|
||||
notifications
|
||||
optimer
|
||||
permissions_tool
|
||||
services
|
||||
srp
|
||||
timerboard
|
||||
|
||||
omit =
|
||||
*/migrations/*
|
||||
*/example/*
|
||||
|
||||
[report]
|
||||
exclude_lines =
|
||||
if self.debug:
|
||||
pragma: no cover
|
||||
raise NotImplementedError
|
||||
if __name__ == .__main__.:
|
||||
def __repr__
|
||||
raise AssertionError
|
||||
|
||||
ignore_errors = True
|
||||
14
.travis.yml
Normal file
@@ -0,0 +1,14 @@
|
||||
language: python
|
||||
python:
|
||||
- "2.7"
|
||||
- "3.5"
|
||||
# command to install dependencies
|
||||
install:
|
||||
- pip install requests
|
||||
- pip install -r requirements.txt
|
||||
- pip install -r testing-requirements.txt
|
||||
# command to run tests
|
||||
script: coverage run runtests.py
|
||||
cache: pip
|
||||
after_success:
|
||||
coveralls
|
||||
@@ -2,11 +2,16 @@ Alliance Auth
|
||||
============
|
||||
|
||||
[](https://gitter.im/R4stl1n/allianceauth?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
[](http://allianceauth.readthedocs.io/en/latest/?badge=latest)
|
||||
[](http://allianceauth.readthedocs.io/?badge=latest)
|
||||
[](https://travis-ci.org/allianceauth/allianceauth)
|
||||
[](https://coveralls.io/github/allianceauth/allianceauth?branch=master)
|
||||
|
||||
|
||||
EVE service auth to help corps, alliances, and coalitions manage services.
|
||||
Built for "The 99 Percent" open for anyone to use.
|
||||
|
||||
[Read the docs here.](http://allianceauth.rtfd.io)
|
||||
|
||||
Special Permissions In Admin:
|
||||
|
||||
auth | user | group_management ( Access to add members to groups within the alliance )
|
||||
|
||||
@@ -1,4 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
|
||||
__version__ = '1.14.1'
|
||||
# This will make sure the app is always imported when
|
||||
# Django starts so that shared_task will use this app.
|
||||
from .celeryapp import app as celery_app # noqa
|
||||
|
||||
__version__ = '1.15.4'
|
||||
NAME = 'Alliance Auth v%s' % __version__
|
||||
|
||||
17
alliance_auth/celeryapp.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from __future__ import absolute_import, unicode_literals
|
||||
import os
|
||||
from celery import Celery
|
||||
|
||||
# set the default Django settings module for the 'celery' program.
|
||||
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'alliance_auth.settings')
|
||||
|
||||
from django.conf import settings # noqa
|
||||
|
||||
app = Celery('alliance_auth')
|
||||
|
||||
# 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')
|
||||
|
||||
# Load task modules from all registered Django app configs.
|
||||
app.autodiscover_tasks(lambda: settings.INSTALLED_APPS)
|
||||
127
alliance_auth/hooks.py
Normal file
@@ -0,0 +1,127 @@
|
||||
"""
|
||||
Copyright (c) 2014 Torchbox Ltd and individual contributors.
|
||||
All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
1. Redistributions of source code must retain the above copyright notice,
|
||||
this list of conditions and the following disclaimer.
|
||||
|
||||
2. Redistributions in binary form must reproduce the above copyright
|
||||
notice, this list of conditions and the following disclaimer in the
|
||||
documentation and/or other materials provided with the distribution.
|
||||
|
||||
3. Neither the name of Torchbox nor the names of its contributors may be used
|
||||
to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
Based on https://github.com/torchbox/wagtail/blob/master/wagtail/wagtailcore/hooks.py
|
||||
"""
|
||||
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from importlib import import_module
|
||||
|
||||
from django.apps import apps
|
||||
from django.utils.module_loading import module_has_submodule
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
_hooks = {} # Dict of Name: Fn's of registered hooks
|
||||
|
||||
_all_hooks_registered = False # If all hooks have been searched for and registered yet
|
||||
|
||||
|
||||
def register(name, fn=None):
|
||||
"""
|
||||
Decorator to register a function as a hook
|
||||
|
||||
Register hook for ``hook_name``. Can be used as a decorator::
|
||||
@register('hook_name')
|
||||
def my_hook(...):
|
||||
pass
|
||||
|
||||
or as a function call::
|
||||
def my_hook(...):
|
||||
pass
|
||||
register('hook_name', my_hook)
|
||||
|
||||
:param name: str Name of the hook/callback to register it as
|
||||
:param fn: function to register in the hook/callback
|
||||
:return: function Decorator if applied as a decorator
|
||||
"""
|
||||
def _hook_add(func):
|
||||
if name not in _hooks:
|
||||
logger.debug("Creating new hook %s" % name)
|
||||
_hooks[name] = []
|
||||
|
||||
logger.debug('Registering hook %s for function %s' % (name, fn))
|
||||
_hooks[name].append(func)
|
||||
|
||||
if fn is None:
|
||||
# Behave like a decorator
|
||||
def decorator(func):
|
||||
_hook_add(func)
|
||||
return func
|
||||
return decorator
|
||||
else:
|
||||
# Behave like a function, just register hook
|
||||
_hook_add(fn)
|
||||
|
||||
|
||||
def get_app_modules():
|
||||
"""
|
||||
Get all the modules of the django app
|
||||
:return: name, module tuple
|
||||
"""
|
||||
for app in apps.get_app_configs():
|
||||
yield app.name, app.module
|
||||
|
||||
|
||||
def get_app_submodules(module_name):
|
||||
"""
|
||||
Get a specific sub module of the app
|
||||
:param module_name: module name to get
|
||||
:return: name, module tuple
|
||||
"""
|
||||
for name, module in get_app_modules():
|
||||
if module_has_submodule(module, module_name):
|
||||
yield name, import_module('{0}.{1}'.format(name, module_name))
|
||||
|
||||
|
||||
def register_all_hooks():
|
||||
"""
|
||||
Register all hooks found in 'auth_hooks' sub modules
|
||||
:return:
|
||||
"""
|
||||
global _all_hooks_registered
|
||||
if not _all_hooks_registered:
|
||||
logger.debug("Searching for hooks")
|
||||
hooks = list(get_app_submodules('auth_hooks'))
|
||||
logger.debug("Got %s hooks" % len(hooks))
|
||||
_all_hooks_registered = True
|
||||
|
||||
|
||||
def get_hooks(name):
|
||||
"""
|
||||
Get all the hook functions for the given hook name
|
||||
:param name: str name of the hook to get the functions for
|
||||
:return: list of hook functions
|
||||
"""
|
||||
register_all_hooks()
|
||||
return _hooks.get(name, [])
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
# -*- coding: UTF-8 -*-
|
||||
"""
|
||||
Django settings for alliance_auth project.
|
||||
|
||||
@@ -12,15 +13,12 @@ https://docs.djangoproject.com/en/1.10/ref/settings/
|
||||
|
||||
import os
|
||||
|
||||
import djcelery
|
||||
|
||||
from django.contrib import messages
|
||||
|
||||
djcelery.setup_loader()
|
||||
from celery.schedules import crontab
|
||||
|
||||
# Celery configuration
|
||||
BROKER_URL = 'redis://localhost:6379/0'
|
||||
CELERYBEAT_SCHEDULER = "djcelery.schedulers.DatabaseScheduler"
|
||||
CELERYBEAT_SCHEDULER = "django_celery_beat.schedulers.DatabaseScheduler"
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
|
||||
@@ -48,7 +46,7 @@ INSTALLED_APPS = [
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'djcelery',
|
||||
'django_celery_beat',
|
||||
'bootstrapform',
|
||||
'authentication',
|
||||
'services',
|
||||
@@ -60,10 +58,27 @@ INSTALLED_APPS = [
|
||||
'optimer',
|
||||
'corputils',
|
||||
'fleetactivitytracking',
|
||||
'fleetup',
|
||||
'notifications',
|
||||
'esi',
|
||||
'permissions_tool',
|
||||
'geelweb.django.navhelper',
|
||||
'bootstrap_pagination',
|
||||
'captcha',
|
||||
|
||||
# Services
|
||||
'services.modules.mumble',
|
||||
'services.modules.discord',
|
||||
'services.modules.discourse',
|
||||
'services.modules.ipboard',
|
||||
'services.modules.ips4',
|
||||
'services.modules.market',
|
||||
'services.modules.openfire',
|
||||
'services.modules.seat',
|
||||
'services.modules.smf',
|
||||
'services.modules.phpbb3',
|
||||
'services.modules.xenforo',
|
||||
'services.modules.teamspeak3',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
@@ -134,6 +149,15 @@ DATABASES = {
|
||||
},
|
||||
}
|
||||
|
||||
# If you have run the authentication.0013_service_modules migration
|
||||
# you will need to set this to True in order to install service modules
|
||||
# which were involved in that migration after it has been run.
|
||||
# If you are on a fresh install with no existing database you can safely
|
||||
# set this to True
|
||||
# If you have not run the authentication.0013_service_modules migration
|
||||
# leave this set to False.
|
||||
SERVICES_MIGRATED = False
|
||||
|
||||
# Password validation
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#auth-password-validators
|
||||
|
||||
@@ -185,12 +209,50 @@ MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger'
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
"default": {
|
||||
"BACKEND": "redis_cache.RedisCache",
|
||||
"LOCATION": "localhost:6379",
|
||||
"OPTIONS": {
|
||||
"DB": 1,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
# Google Recaptcha
|
||||
|
||||
CAPTCHA_ENABLED = False
|
||||
|
||||
RECAPTCHA_PUBLIC_KEY = 'MyRecaptchaKey'
|
||||
RECAPTCHA_PRIVATE_KEY = 'MyRecaptchaPrivateKey'
|
||||
|
||||
NOCAPTCHA = True
|
||||
|
||||
#####################################################
|
||||
##
|
||||
## Auth configuration starts here
|
||||
##
|
||||
#####################################################
|
||||
|
||||
|
||||
#########################
|
||||
# CELERY SCHEDULED TASKS
|
||||
#########################
|
||||
CELERYBEAT_SCHEDULE = {
|
||||
'run_api_refresh': {
|
||||
'task': 'eveonline.tasks.run_api_refresh',
|
||||
'schedule': crontab(minute=0, hour="*/3"),
|
||||
},
|
||||
'run_corp_update': {
|
||||
'task': 'eveonline.tasks.run_corp_update',
|
||||
'schedule': crontab(minute=0, hour="*/2"),
|
||||
},
|
||||
'update_all_corpstats': {
|
||||
'task': 'corputils.tasks.update_all_corpstats',
|
||||
'schedule': crontab(minute=0, hour="*/6"),
|
||||
},
|
||||
}
|
||||
|
||||
#################
|
||||
# EMAIL SETTINGS
|
||||
#################
|
||||
@@ -201,7 +263,7 @@ MESSAGE_TAGS = {
|
||||
# EMAIL_HOST_PASSWORD - Email Password
|
||||
# EMAIL_USE_TLS - Set to use TLS encryption
|
||||
#################
|
||||
DOMAIN = os.environ.get('AA_DOMAIN', 'https://yourdomain.com')
|
||||
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
|
||||
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.gmail.com')
|
||||
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
|
||||
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
|
||||
@@ -226,7 +288,7 @@ SITE_NAME = os.environ.get('AA_SITE_NAME', 'Alliance Auth')
|
||||
###################
|
||||
# Get client ID and client secret from registering an app at
|
||||
# https://developers.eveonline.com/
|
||||
# Callback URL should be https://mydomain.com/sso/callback
|
||||
# Callback URL should be https://example.com/sso/callback
|
||||
###################
|
||||
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
|
||||
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
|
||||
@@ -247,60 +309,6 @@ MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', '
|
||||
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
|
||||
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
|
||||
|
||||
#########################
|
||||
# Alliance Service Setup
|
||||
#########################
|
||||
# ENABLE_AUTH_FORUM - Enable forum support in the auth for auth'd members
|
||||
# ENABLE_AUTH_JABBER - Enable jabber support in the auth for auth'd members
|
||||
# ENABLE_AUTH_MUMBLE - Enable mumble support in the auth for auth'd members
|
||||
# ENABLE_AUTH_IPBOARD - Enable IPBoard forum support in the auth for auth'd members
|
||||
# ENABLE_AUTH_DISCORD - Enable Discord support in the auth for auth'd members
|
||||
# ENABLE_AUTH_DISCOURSE - Enable Discourse support in the auth for auth'd members
|
||||
# ENABLE_AUTH_IPS4 - Enable IPS4 support in the auth for auth'd members
|
||||
# ENABLE_AUTH_SMF - Enable SMF forum support in the auth for auth'd members
|
||||
# ENABLE_AUTH_MARKET = Enable Alliance Market support in auth for auth'd members
|
||||
# ENABLE_AUTH_PATHFINDER = Enable Alliance Pathfinder suppor in auth for auth'd members
|
||||
# ENABLE_AUTH_XENFORO = Enable XenForo forums support in the auth for auth'd members
|
||||
#########################
|
||||
ENABLE_AUTH_FORUM = 'True' == os.environ.get('AA_ENABLE_AUTH_FORUM', 'False')
|
||||
ENABLE_AUTH_JABBER = 'True' == os.environ.get('AA_ENABLE_AUTH_JABBER', 'False')
|
||||
ENABLE_AUTH_MUMBLE = 'True' == os.environ.get('AA_ENABLE_AUTH_MUMBLE', 'False')
|
||||
ENABLE_AUTH_IPBOARD = 'True' == os.environ.get('AA_ENABLE_AUTH_IPBOARD', 'False')
|
||||
ENABLE_AUTH_TEAMSPEAK3 = 'True' == os.environ.get('AA_ENABLE_AUTH_TEAMSPEAK3', 'False')
|
||||
ENABLE_AUTH_DISCORD = 'True' == os.environ.get('AA_ENABLE_AUTH_DISCORD', 'False')
|
||||
ENABLE_AUTH_DISCOURSE = 'True' == os.environ.get('AA_ENABLE_AUTH_DISCOURSE', 'False')
|
||||
ENABLE_AUTH_IPS4 = 'True' == os.environ.get('AA_ENABLE_AUTH_IPS4', 'False')
|
||||
ENABLE_AUTH_SMF = 'True' == os.environ.get('AA_ENABLE_AUTH_SMF', 'False')
|
||||
ENABLE_AUTH_MARKET = 'True' == os.environ.get('AA_ENABLE_AUTH_MARKET', 'False')
|
||||
ENABLE_AUTH_XENFORO = 'True' == os.environ.get('AA_ENABLE_AUTH_XENFORO', 'False')
|
||||
|
||||
#####################
|
||||
# Blue service Setup
|
||||
#####################
|
||||
# ENABLE_BLUE_FORUM - Enable forum support in the auth for blues
|
||||
# ENABLE_BLUE_JABBER - Enable jabber support in the auth for blues
|
||||
# ENABLE_BLUE_MUMBLE - Enable mumble support in the auth for blues
|
||||
# ENABLE_BLUE_IPBOARD - Enable IPBoard forum support in the auth for blues
|
||||
# ENABLE_BLUE_DISCORD - Enable Discord support in the auth for blues
|
||||
# ENABLE_BLUE_DISCOURSE - Enable Discord support in the auth for blues
|
||||
# ENABLE_BLUE_IPS4 - Enable IPS4 forum support in the auth for blues
|
||||
# ENABLE_BLUE_SMF - Enable SMF forum support in the auth for blues
|
||||
# ENABLE_BLUE_MARKET - Enable Alliance Market in the auth for blues
|
||||
# ENABLE_BLUE_PATHFINDER = Enable Pathfinder support in the auth for blues
|
||||
# ENABLE_BLUE_XENFORO = Enable XenForo forum support in the auth for blue
|
||||
#####################
|
||||
ENABLE_BLUE_FORUM = 'True' == os.environ.get('AA_ENABLE_BLUE_FORUM', 'False')
|
||||
ENABLE_BLUE_JABBER = 'True' == os.environ.get('AA_ENABLE_BLUE_JABBER', 'False')
|
||||
ENABLE_BLUE_MUMBLE = 'True' == os.environ.get('AA_ENABLE_BLUE_MUMBLE', 'False')
|
||||
ENABLE_BLUE_IPBOARD = 'True' == os.environ.get('AA_ENABLE_BLUE_IPBOARD', 'False')
|
||||
ENABLE_BLUE_TEAMSPEAK3 = 'True' == os.environ.get('AA_ENABLE_BLUE_TEAMSPEAK3', 'False')
|
||||
ENABLE_BLUE_DISCORD = 'True' == os.environ.get('AA_ENABLE_BLUE_DISCORD', 'False')
|
||||
ENABLE_BLUE_DISCOURSE = 'True' == os.environ.get('AA_ENABLE_BLUE_DISCOURSE', 'False')
|
||||
ENABLE_BLUE_IPS4 = 'True' == os.environ.get('AA_ENABLE_BLUE_IPS4', 'False')
|
||||
ENABLE_BLUE_SMF = 'True' == os.environ.get('AA_ENABLE_BLUE_SMF', 'False')
|
||||
ENABLE_BLUE_MARKET = 'True' == os.environ.get('AA_ENABLE_BLUE_MARKET', 'False')
|
||||
ENABLE_BLUE_XENFORO = 'True' == os.environ.get('AA_ENABLE_BLUE_XENFORO', 'False')
|
||||
|
||||
#########################
|
||||
# Tenant Configuration
|
||||
#########################
|
||||
@@ -319,11 +327,15 @@ ALLIANCE_IDS = []
|
||||
# CORP_API_VCODE - Set this to the api vcode for the corp API key
|
||||
# BLUE_STANDING - The lowest standings value to consider blue
|
||||
# STANDING_LEVEL - The level of standings to query. Accepted values are 'corp' and 'alliance'.
|
||||
# BLUE_CORP_IDS - A list of corps to remain blue regardless of in-game standings
|
||||
# BLUE_ALLIANCE_IDS - A list of alliances to remain blue regardless of in-game standings
|
||||
########################
|
||||
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
|
||||
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
|
||||
BLUE_STANDING = float(os.environ.get('AA_BLUE_STANDING', '5.0'))
|
||||
STANDING_LEVEL = os.environ.get('AA_STANDING_LEVEL', 'corp')
|
||||
BLUE_CORP_IDS = []
|
||||
BLUE_ALLIANCE_IDS = []
|
||||
|
||||
########################
|
||||
# API Configuration
|
||||
@@ -351,21 +363,23 @@ API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
|
||||
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
|
||||
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
|
||||
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
|
||||
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
|
||||
#
|
||||
# Available soruces are 'esi' and 'xml'
|
||||
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
|
||||
#######################
|
||||
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', 'esi')
|
||||
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', 'esi')
|
||||
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', 'esi')
|
||||
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', '')
|
||||
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', '')
|
||||
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', '')
|
||||
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', '')
|
||||
|
||||
#####################
|
||||
# Alliance Market
|
||||
#####################
|
||||
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://yourdomain.com/market')
|
||||
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://example.com/market')
|
||||
MARKET_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_market',
|
||||
'USER': os.environ.get('AA_DB_MARKET_USER', 'allianceserver'),
|
||||
'USER': os.environ.get('AA_DB_MARKET_USER', 'allianceserver-market'),
|
||||
'PASSWORD': os.environ.get('AA_DB_MARKET_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_MARKET_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_MARKET_PORT', '3306'),
|
||||
@@ -374,10 +388,16 @@ MARKET_DB = {
|
||||
#####################
|
||||
# HR Configuration
|
||||
#####################
|
||||
# JACK_KNIFE_URL - Url for the audit page of API Jack knife
|
||||
# Should seriously replace with your own.
|
||||
# API_KEY_AUDIT_URL - URL for viewing API keys.
|
||||
# Other URLs are supported. Use string formatting notation {} with parameter names api_id, vcode, pk
|
||||
# Example URL formats are shown below:
|
||||
# Old Jacknife: 'https://example.com/jacknife/eveapi/audit.php?usid={api_id}&apik={vcode}'
|
||||
# New Jacknife: 'https://example.com/jacknife/?usid={api_id}&apik={vcode}'
|
||||
# SeAT: 'https://seat.example.com/api-key/detail/{api_id}'
|
||||
# Django Admin: '/admin/eveonline/eveapikeypair/{pk}/change'
|
||||
# Leave blank for the verification code to be shown in a popup on click.
|
||||
#####################
|
||||
JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://ridetheclown.com/eveapi/audit.php')
|
||||
API_KEY_AUDIT_URL = os.environ.get('AA_API_KEY_AUDIT_URL', '')
|
||||
|
||||
#####################
|
||||
# Forum Configuration
|
||||
@@ -386,14 +406,14 @@ JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://ridetheclown.com/ev
|
||||
# IPBOARD_APIKEY - Api key to interact with ipboard
|
||||
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
|
||||
#####################
|
||||
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'yourdomain.com/interface/board/index.php')
|
||||
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
|
||||
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
|
||||
IPBOARD_APIMODULE = 'aa'
|
||||
|
||||
########################
|
||||
# XenForo Configuration
|
||||
########################
|
||||
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'yourdomain.com/api.php')
|
||||
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
|
||||
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
|
||||
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
|
||||
#####################
|
||||
@@ -410,10 +430,10 @@ XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
|
||||
# BROADCAST_USER - Broadcast user JID
|
||||
# BROADCAST_USER_PASSWORD - Broadcast user password
|
||||
######################
|
||||
JABBER_URL = os.environ.get('AA_JABBER_URL', "yourdomain.com")
|
||||
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
|
||||
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
|
||||
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "yourdomain.com")
|
||||
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://yourdomain.com:9090")
|
||||
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
|
||||
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
|
||||
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
|
||||
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
|
||||
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
|
||||
@@ -422,11 +442,10 @@ BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast"
|
||||
######################################
|
||||
# Mumble Configuration
|
||||
######################################
|
||||
# MUMBLE_URL - Mumble server url
|
||||
# MUMBLE_SERVER_ID - Mumble server id
|
||||
# MUMBLE_URL - Mumble server host
|
||||
# Do not include leading http:// or mumble://
|
||||
######################################
|
||||
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "yourdomain.com")
|
||||
MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
|
||||
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
|
||||
|
||||
######################################
|
||||
# PHPBB3 Configuration
|
||||
@@ -434,7 +453,7 @@ MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
|
||||
PHPBB3_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_forum',
|
||||
'USER': os.environ.get('AA_DB_PHPBB3_USER', 'allianceserver'),
|
||||
'USER': os.environ.get('AA_DB_PHPBB3_USER', 'allianceserver-phpbb3'),
|
||||
'PASSWORD': os.environ.get('AA_DB_PHPBB3_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_PHPBB3_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_PHPBB3_PORT', '3306'),
|
||||
@@ -456,7 +475,7 @@ TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'
|
||||
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
|
||||
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
|
||||
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
|
||||
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'yourdomain.com')
|
||||
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
|
||||
|
||||
######################################
|
||||
# Discord Configuration
|
||||
@@ -474,7 +493,7 @@ DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', '')
|
||||
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', '')
|
||||
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', '')
|
||||
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', '')
|
||||
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://mydomain.com/discord_callback')
|
||||
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
|
||||
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
|
||||
|
||||
######################################
|
||||
@@ -496,26 +515,34 @@ DISCOURSE_SSO_SECRET = os.environ.get('AA_DISCOURSE_SSO_SECRET', '')
|
||||
# IPS4_URL - base url of the IPS4 install (no trailing slash)
|
||||
# IPS4_API_KEY - API key provided by IPS4
|
||||
#####################################
|
||||
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://yourdomain.com/ips4')
|
||||
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
|
||||
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
|
||||
IPS4_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_ips4',
|
||||
'USER': os.environ.get('AA_DB_IPS4_USER', 'allianceserver'),
|
||||
'USER': os.environ.get('AA_DB_IPS4_USER', 'allianceserver-ips4'),
|
||||
'PASSWORD': os.environ.get('AA_DB_IPS4_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_IPS4_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_IPS4_PORT', '3306'),
|
||||
}
|
||||
|
||||
#####################################
|
||||
# SEAT Configuration
|
||||
#####################################
|
||||
# SEAT_URL - base url of the seat install (no trailing slash)
|
||||
# SEAT_XTOKEN - API key X-Token provided by SeAT
|
||||
#####################################
|
||||
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
|
||||
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', '')
|
||||
|
||||
######################################
|
||||
# SMF Configuration
|
||||
######################################
|
||||
SMF_URL = os.environ.get('AA_SMF_URL', '')
|
||||
SMF_URL = os.environ.get('AA_SMF_URL', 'https://example.com')
|
||||
SMF_DB = {
|
||||
'ENGINE': 'django.db.backends.mysql',
|
||||
'NAME': 'alliance_smf',
|
||||
'USER': os.environ.get('AA_DB_SMF_USER', 'allianceserver'),
|
||||
'USER': os.environ.get('AA_DB_SMF_USER', 'allianceserver-smf'),
|
||||
'PASSWORD': os.environ.get('AA_DB_SMF_PASSWORD', 'password'),
|
||||
'HOST': os.environ.get('AA_DB_SMF_HOST', '127.0.0.1'),
|
||||
'PORT': os.environ.get('AA_DB_SMF_PORT', '3306'),
|
||||
@@ -636,6 +663,10 @@ LOGGING = {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
'fleetup': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'util': {
|
||||
'handlers': ['log_file', 'console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
@@ -648,15 +679,30 @@ LOGGING = {
|
||||
}
|
||||
|
||||
# Conditionally add databases only if configured
|
||||
if ENABLE_AUTH_FORUM or ENABLE_BLUE_FORUM:
|
||||
if 'services.modules.phpbb3' in INSTALLED_APPS:
|
||||
DATABASES['phpbb3'] = PHPBB3_DB
|
||||
if ENABLE_AUTH_SMF or ENABLE_BLUE_SMF:
|
||||
if 'services.modules.smf' in INSTALLED_APPS:
|
||||
DATABASES['smf'] = SMF_DB
|
||||
if ENABLE_AUTH_MARKET or ENABLE_BLUE_MARKET:
|
||||
if 'services.modules.market' in INSTALLED_APPS:
|
||||
DATABASES['market'] = MARKET_DB
|
||||
if ENABLE_AUTH_IPS4 or ENABLE_BLUE_IPS4:
|
||||
if 'services.modules.ips4' in INSTALLED_APPS:
|
||||
DATABASES['ips4'] = IPS4_DB
|
||||
|
||||
# Ensure corp/alliance IDs are expected types
|
||||
STR_CORP_IDS = [str(id) for id in CORP_IDS]
|
||||
STR_ALLIANCE_IDS = [str(id) for id in ALLIANCE_IDS]
|
||||
STR_BLUE_CORP_IDS = [str(id) for id in BLUE_CORP_IDS]
|
||||
STR_BLUE_ALLIANCE_IDS = [str(id) for id in BLUE_ALLIANCE_IDS]
|
||||
|
||||
# Conditionally add periodic tasks for services if installed
|
||||
if 'services.modules.teamspeak3' in INSTALLED_APPS:
|
||||
CELERYBEAT_SCHEDULE['run_ts3_group_update'] = {
|
||||
'task': 'services.modules.teamspeak3.tasks.run_ts3_group_update',
|
||||
'schedule': crontab(minute='*/30'),
|
||||
}
|
||||
|
||||
if 'services.modules.seat' in INSTALLED_APPS:
|
||||
CELERYBEAT_SCHEDULE['run_seat_api_sync'] = {
|
||||
'task': 'services.modules.seat.tasks.run_api_sync',
|
||||
'schedule': crontab(minute='*/30'),
|
||||
}
|
||||
|
||||
0
fleetup/tests.py → alliance_auth/tests/__init__.py
Executable file → Normal file
98
alliance_auth/tests/auth_utils.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db.models.signals import m2m_changed, pre_save
|
||||
from django.contrib.auth.models import User, Group, Permission
|
||||
|
||||
from services.signals import m2m_changed_user_groups, pre_save_user
|
||||
from services.signals import m2m_changed_group_permissions, m2m_changed_user_permissions
|
||||
from authentication.signals import pre_save_auth_state
|
||||
|
||||
from authentication.tasks import make_member, make_blue
|
||||
from authentication.models import AuthServicesInfo
|
||||
from authentication.states import MEMBER_STATE, BLUE_STATE, NONE_STATE
|
||||
|
||||
from eveonline.models import EveCharacter
|
||||
|
||||
|
||||
class AuthUtils:
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@staticmethod
|
||||
def _create_user(username):
|
||||
return User.objects.create(username=username)
|
||||
|
||||
@classmethod
|
||||
def create_user(cls, username, disconnect_signals=False):
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
user.authservicesinfo.state = NONE_STATE
|
||||
user.authservicesinfo.save()
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def create_member(cls, username):
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
user.authservicesinfo.state = MEMBER_STATE
|
||||
user.authservicesinfo.save()
|
||||
make_member(user.authservicesinfo)
|
||||
cls.connect_signals()
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def create_blue(cls, username):
|
||||
cls.disconnect_signals()
|
||||
user = cls._create_user(username)
|
||||
user.authservicesinfo.state = BLUE_STATE
|
||||
user.authservicesinfo.save()
|
||||
make_blue(user.authservicesinfo)
|
||||
cls.connect_signals()
|
||||
return user
|
||||
|
||||
@classmethod
|
||||
def disconnect_signals(cls):
|
||||
m2m_changed.disconnect(m2m_changed_user_groups, sender=User.groups.through)
|
||||
m2m_changed.disconnect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
||||
m2m_changed.disconnect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
||||
pre_save.disconnect(pre_save_user, sender=User)
|
||||
pre_save.disconnect(pre_save_auth_state, sender=AuthServicesInfo)
|
||||
|
||||
@classmethod
|
||||
def connect_signals(cls):
|
||||
m2m_changed.connect(m2m_changed_user_groups, sender=User.groups.through)
|
||||
m2m_changed.connect(m2m_changed_group_permissions, sender=Group.permissions.through)
|
||||
m2m_changed.connect(m2m_changed_user_permissions, sender=User.user_permissions.through)
|
||||
pre_save.connect(pre_save_user, sender=User)
|
||||
pre_save.connect(pre_save_auth_state, sender=AuthServicesInfo)
|
||||
|
||||
@classmethod
|
||||
def add_main_character(cls, user, name, character_id, corp_id='', corp_name='', corp_ticker='', alliance_id='',
|
||||
alliance_name=''):
|
||||
EveCharacter.objects.create(
|
||||
character_id=character_id,
|
||||
character_name=name,
|
||||
corporation_id=corp_id,
|
||||
corporation_name=corp_name,
|
||||
corporation_ticker=corp_ticker,
|
||||
alliance_id=alliance_id,
|
||||
alliance_name=alliance_name,
|
||||
api_id='1234',
|
||||
user=user
|
||||
)
|
||||
AuthServicesInfo.objects.update_or_create(user=user, defaults={'main_char_id': character_id})
|
||||
|
||||
@classmethod
|
||||
def add_permissions_to_groups(cls, perms, groups, disconnect_signals=True):
|
||||
if disconnect_signals:
|
||||
cls.disconnect_signals()
|
||||
|
||||
for group in groups:
|
||||
for perm in perms:
|
||||
group.permissions.add(perm)
|
||||
|
||||
if disconnect_signals:
|
||||
cls.connect_signals()
|
||||
556
alliance_auth/tests/test_settings.py
Normal file
@@ -0,0 +1,556 @@
|
||||
"""
|
||||
Alliance Auth Test Suite Django settings.
|
||||
"""
|
||||
|
||||
import os
|
||||
|
||||
from django.contrib import messages
|
||||
|
||||
import alliance_auth
|
||||
|
||||
# Use nose to run all tests
|
||||
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
|
||||
|
||||
NOSE_ARGS = [
|
||||
#'--with-coverage',
|
||||
#'--cover-package=',
|
||||
#'--exe', # If your tests need this to be found/run, check they py files are not chmodded +x
|
||||
]
|
||||
|
||||
# Celery configuration
|
||||
CELERY_ALWAYS_EAGER = True # Forces celery to run locally for testing
|
||||
|
||||
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
|
||||
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(alliance_auth.__file__)))
|
||||
|
||||
SECRET_KEY = 'testing only'
|
||||
|
||||
DEBUG = True
|
||||
|
||||
# Application definition
|
||||
|
||||
INSTALLED_APPS = [
|
||||
'django.contrib.admin',
|
||||
'django.contrib.auth',
|
||||
'django.contrib.contenttypes',
|
||||
'django.contrib.sessions',
|
||||
'django.contrib.messages',
|
||||
'django.contrib.staticfiles',
|
||||
'django.contrib.humanize',
|
||||
'django_celery_beat',
|
||||
'bootstrapform',
|
||||
'authentication',
|
||||
'services',
|
||||
'eveonline',
|
||||
'groupmanagement',
|
||||
'hrapplications',
|
||||
'timerboard',
|
||||
'srp',
|
||||
'optimer',
|
||||
'corputils',
|
||||
'fleetactivitytracking',
|
||||
'fleetup',
|
||||
'notifications',
|
||||
'esi',
|
||||
'permissions_tool',
|
||||
'geelweb.django.navhelper',
|
||||
'bootstrap_pagination',
|
||||
'services.modules.mumble',
|
||||
'services.modules.discord',
|
||||
'services.modules.discourse',
|
||||
'services.modules.ipboard',
|
||||
'services.modules.ips4',
|
||||
'services.modules.market',
|
||||
'services.modules.openfire',
|
||||
'services.modules.seat',
|
||||
'services.modules.smf',
|
||||
'services.modules.phpbb3',
|
||||
'services.modules.xenforo',
|
||||
'services.modules.teamspeak3',
|
||||
'django_nose',
|
||||
]
|
||||
|
||||
MIDDLEWARE = [
|
||||
'django.middleware.security.SecurityMiddleware',
|
||||
'django.contrib.sessions.middleware.SessionMiddleware',
|
||||
'django.middleware.common.CommonMiddleware',
|
||||
'django.middleware.csrf.CsrfViewMiddleware',
|
||||
'django.contrib.auth.middleware.AuthenticationMiddleware',
|
||||
'django.contrib.messages.middleware.MessageMiddleware',
|
||||
'django.middleware.clickjacking.XFrameOptionsMiddleware',
|
||||
'django.middleware.locale.LocaleMiddleware',
|
||||
]
|
||||
|
||||
ROOT_URLCONF = 'alliance_auth.urls'
|
||||
|
||||
LOCALE_PATHS = (
|
||||
os.path.join(BASE_DIR, 'locale/'),
|
||||
)
|
||||
|
||||
ugettext = lambda s: s
|
||||
LANGUAGES = (
|
||||
('en', ugettext('English')),
|
||||
('de', ugettext('German')),
|
||||
)
|
||||
|
||||
TEMPLATES = [
|
||||
{
|
||||
'BACKEND': 'django.template.backends.django.DjangoTemplates',
|
||||
'DIRS': [
|
||||
os.path.join(BASE_DIR, 'customization/templates'),
|
||||
os.path.join(BASE_DIR, 'stock/templates'),
|
||||
],
|
||||
'APP_DIRS': True,
|
||||
'OPTIONS': {
|
||||
'context_processors': [
|
||||
'django.template.context_processors.debug',
|
||||
'django.template.context_processors.request',
|
||||
'django.contrib.auth.context_processors.auth',
|
||||
'django.contrib.messages.context_processors.messages',
|
||||
'django.template.context_processors.i18n',
|
||||
'django.template.context_processors.media',
|
||||
'django.template.context_processors.static',
|
||||
'django.template.context_processors.tz',
|
||||
'services.context_processors.auth_settings',
|
||||
'notifications.context_processors.user_notification_count',
|
||||
'authentication.context_processors.states',
|
||||
'authentication.context_processors.membership_state',
|
||||
'groupmanagement.context_processors.can_manage_groups',
|
||||
],
|
||||
},
|
||||
},
|
||||
]
|
||||
|
||||
# Database
|
||||
# https://docs.djangoproject.com/en/1.10/ref/settings/#databases
|
||||
|
||||
DATABASES = {
|
||||
'default': {
|
||||
'ENGINE': 'django.db.backends.sqlite3',
|
||||
'NAME': 'alliance_auth',
|
||||
'USER': os.environ.get('AA_DB_DEFAULT_USER', None),
|
||||
'PASSWORD': os.environ.get('AA_DB_DEFAULT_PASSWORD', None),
|
||||
'HOST': os.environ.get('AA_DB_DEFAULT_HOST', None)
|
||||
},
|
||||
}
|
||||
|
||||
LOGIN_URL = 'auth_login_user'
|
||||
|
||||
SUPERUSER_STATE_BYPASS = 'True' == os.environ.get('AA_SUPERUSER_STATE_BYPASS', 'True')
|
||||
|
||||
# Internationalization
|
||||
# https://docs.djangoproject.com/en/1.10/topics/i18n/
|
||||
|
||||
LANGUAGE_CODE = os.environ.get('AA_LANGUAGE_CODE', 'en')
|
||||
|
||||
TIME_ZONE = os.environ.get('AA_TIME_ZONE', 'UTC')
|
||||
|
||||
USE_I18N = True
|
||||
|
||||
USE_L10N = True
|
||||
|
||||
USE_TZ = True
|
||||
|
||||
|
||||
# Static files (CSS, JavaScript, Images)
|
||||
|
||||
STATIC_URL = '/static/'
|
||||
STATIC_ROOT = os.path.join(BASE_DIR, "static")
|
||||
STATICFILES_DIRS = (
|
||||
os.path.join(BASE_DIR, "customization/static"),
|
||||
os.path.join(BASE_DIR, "stock/static"),
|
||||
)
|
||||
|
||||
# Bootstrap messaging css workaround
|
||||
MESSAGE_TAGS = {
|
||||
messages.ERROR: 'danger'
|
||||
}
|
||||
|
||||
CACHES = {
|
||||
'default': {
|
||||
'BACKEND': 'django.core.cache.backends.dummy.DummyCache',
|
||||
}
|
||||
}
|
||||
|
||||
#####################################################
|
||||
##
|
||||
## Auth configuration starts here
|
||||
##
|
||||
#####################################################
|
||||
|
||||
###########################
|
||||
# ALLIANCE / CORP TOGGLE
|
||||
###########################
|
||||
# Specifies to run membership checks against corp or alliance
|
||||
# Set to FALSE for alliance
|
||||
# Set to TRUE for corp
|
||||
###########################
|
||||
IS_CORP = 'True' == os.environ.get('AA_IS_CORP', 'True')
|
||||
|
||||
#################
|
||||
# EMAIL SETTINGS
|
||||
#################
|
||||
# DOMAIN - The alliance auth domain_url
|
||||
# EMAIL_HOST - SMTP Server URL
|
||||
# EMAIL_PORT - SMTP Server PORT
|
||||
# EMAIL_HOST_USER - Email Username (for gmail, the entire address)
|
||||
# EMAIL_HOST_PASSWORD - Email Password
|
||||
# EMAIL_USE_TLS - Set to use TLS encryption
|
||||
#################
|
||||
DOMAIN = os.environ.get('AA_DOMAIN', 'https://example.com')
|
||||
EMAIL_HOST = os.environ.get('AA_EMAIL_HOST', 'smtp.example.com')
|
||||
EMAIL_PORT = int(os.environ.get('AA_EMAIL_PORT', '587'))
|
||||
EMAIL_HOST_USER = os.environ.get('AA_EMAIL_HOST_USER', '')
|
||||
EMAIL_HOST_PASSWORD = os.environ.get('AA_EMAIL_HOST_PASSWORD', '')
|
||||
EMAIL_USE_TLS = 'True' == os.environ.get('AA_EMAIL_USE_TLS', 'True')
|
||||
|
||||
####################
|
||||
# Front Page Links
|
||||
####################
|
||||
# KILLBOARD_URL - URL for your killboard. Blank to hide link
|
||||
# MEDIA_URL - URL for your media page (youtube etc). Blank to hide link
|
||||
# FORUM_URL - URL for your forums. Blank to hide link
|
||||
# SITE_NAME - Name of the auth site.
|
||||
####################
|
||||
KILLBOARD_URL = os.environ.get('AA_KILLBOARD_URL', '')
|
||||
EXTERNAL_MEDIA_URL = os.environ.get('AA_EXTERNAL_MEDIA_URL', '')
|
||||
FORUM_URL = os.environ.get('AA_FORUM_URL', '')
|
||||
SITE_NAME = os.environ.get('AA_SITE_NAME', 'Test Alliance Auth')
|
||||
|
||||
###################
|
||||
# SSO Settings
|
||||
###################
|
||||
# Optional SSO.
|
||||
# Get client ID and client secret from registering an app at
|
||||
# https://developers.eveonline.com/
|
||||
# Callback URL should be http://mydomain.com/sso/callback
|
||||
# Leave callback blank to hide SSO button on login page
|
||||
###################
|
||||
ESI_SSO_CLIENT_ID = os.environ.get('AA_ESI_SSO_CLIENT_ID', '')
|
||||
ESI_SSO_CLIENT_SECRET = os.environ.get('AA_ESI_SSO_CLIENT_SECRET', '')
|
||||
ESI_SSO_CALLBACK_URL = os.environ.get('AA_ESI_SSO_CALLBACK_URL', '')
|
||||
|
||||
#########################
|
||||
# Default Group Settings
|
||||
#########################
|
||||
# DEFAULT_AUTH_GROUP - Default group members are put in
|
||||
# DEFAULT_BLUE_GROUP - Default group for blue members
|
||||
# MEMBER_CORP_GROUPS - Assign members to a group representing their main corp
|
||||
# BLUE_CORP_GROUPS - Assign blues to a group representing their main corp
|
||||
#########################
|
||||
DEFAULT_AUTH_GROUP = os.environ.get('AA_DEFAULT_ALLIANCE_GROUP', 'Member')
|
||||
DEFAULT_BLUE_GROUP = os.environ.get('AA_DEFAULT_BLUE_GROUP', 'Blue')
|
||||
MEMBER_CORP_GROUPS = 'True' == os.environ.get('AA_MEMBER_CORP_GROUPS', 'True')
|
||||
MEMBER_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_MEMBER_ALLIANCE_GROUPS', 'False')
|
||||
BLUE_CORP_GROUPS = 'True' == os.environ.get('AA_BLUE_CORP_GROUPS', 'False')
|
||||
BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'False')
|
||||
|
||||
#########################
|
||||
# Corp Configuration
|
||||
#########################
|
||||
# If running in alliance mode, the following should be for the executor corp#
|
||||
# CORP_ID - Set this to your corp ID (get this from https://zkillboard.com/corporation/#######)
|
||||
# CORP_NAME - Set this to your Corporation Name
|
||||
# CORP_API_ID - Set this to the api id for the corp API key
|
||||
# CORP_API_VCODE - Set this to the api vcode for the corp API key
|
||||
########################
|
||||
CORP_ID = os.environ.get('AA_CORP_ID', '1234')
|
||||
CORP_NAME = os.environ.get('AA_CORP_NAME', 'Alliance Auth Test Corp')
|
||||
CORP_API_ID = os.environ.get('AA_CORP_API_ID', '')
|
||||
CORP_API_VCODE = os.environ.get('AA_CORP_API_VCODE', '')
|
||||
|
||||
#########################
|
||||
# Alliance Configuration
|
||||
#########################
|
||||
# ALLIANCE_ID - Set this to your Alliance ID (get this from https://zkillboard.com/alliance/#######)
|
||||
# ALLIANCE_NAME - Set this to your Alliance Name
|
||||
########################
|
||||
ALLIANCE_ID = os.environ.get('AA_ALLIANCE_ID', '12345')
|
||||
ALLIANCE_NAME = os.environ.get('AA_ALLIANCE_NAME', 'Alliance Auth Test Alliance')
|
||||
|
||||
########################
|
||||
# API Configuration
|
||||
########################
|
||||
# MEMBER_API_MASK - Numeric value of minimum API mask required for members
|
||||
# MEMBER_API_ACCOUNT - Require API to be for Account and not character restricted
|
||||
# BLUE_API_MASK - Numeric value of minimum API mask required for blues
|
||||
# BLUE_API_ACCOUNT - Require API to be for Account and not character restricted
|
||||
# REJECT_OLD_APIS - Require each submitted API be newer than the latest submitted API
|
||||
# REJECT_OLD_APIS_MARGIN - Margin from latest submitted API ID within which a newly submitted API is still accepted
|
||||
# API_SSO_VALIDATION - Require users to prove ownership of newly entered API keys via SSO
|
||||
# Requires SSO to be configured.
|
||||
#######################
|
||||
MEMBER_API_MASK = os.environ.get('AA_MEMBER_API_MASK', 268435455)
|
||||
MEMBER_API_ACCOUNT = 'True' == os.environ.get('AA_MEMBER_API_ACCOUNT', 'True')
|
||||
BLUE_API_MASK = os.environ.get('AA_BLUE_API_MASK', 8388608)
|
||||
BLUE_API_ACCOUNT = 'True' == os.environ.get('AA_BLUE_API_ACCOUNT', 'True')
|
||||
REJECT_OLD_APIS = 'True' == os.environ.get('AA_REJECT_OLD_APIS', 'False')
|
||||
REJECT_OLD_APIS_MARGIN = os.environ.get('AA_REJECT_OLD_APIS_MARGIN', 50)
|
||||
API_SSO_VALIDATION = 'True' == os.environ.get('AA_API_SSO_VALIDATION', 'False')
|
||||
|
||||
#######################
|
||||
# EVE Provider Settings
|
||||
#######################
|
||||
# EVEONLINE_CHARACTER_PROVIDER - Name of default data source for getting eve character data
|
||||
# EVEONLINE_CORP_PROVIDER - Name of default data source for getting eve corporation data
|
||||
# EVEONLINE_ALLIANCE_PROVIDER - Name of default data source for getting eve alliance data
|
||||
# EVEONLINE_ITEMTYPE_PROVIDER - Name of default data source for getting eve item type data
|
||||
#
|
||||
# Available sources are 'esi' and 'xml'. Leaving blank results in the default 'esi' being used.
|
||||
#######################
|
||||
EVEONLINE_CHARACTER_PROVIDER = os.environ.get('AA_EVEONLINE_CHARACTER_PROVIDER', 'xml')
|
||||
EVEONLINE_CORP_PROVIDER = os.environ.get('AA_EVEONLINE_CORP_PROVIDER', 'xml')
|
||||
EVEONLINE_ALLIANCE_PROVIDER = os.environ.get('AA_EVEONLINE_ALLIANCE_PROVIDER', 'xml')
|
||||
EVEONLINE_ITEMTYPE_PROVIDER = os.environ.get('AA_EVEONLINE_ITEMTYPE_PROVIDER', 'xml')
|
||||
|
||||
#####################
|
||||
# Alliance Market
|
||||
#####################
|
||||
MARKET_URL = os.environ.get('AA_MARKET_URL', 'http://yourdomain.com/market')
|
||||
|
||||
#####################
|
||||
# HR Configuration
|
||||
#####################
|
||||
# JACK_KNIFE_URL - Url for the audit page of API Jack knife
|
||||
# Should seriously replace with your own.
|
||||
#####################
|
||||
JACK_KNIFE_URL = os.environ.get('AA_JACK_KNIFE_URL', 'http://example.com/eveapi/audit.php')
|
||||
|
||||
#####################
|
||||
# Forum Configuration
|
||||
#####################
|
||||
# IPBOARD_ENDPOINT - Api endpoint if using ipboard
|
||||
# IPBOARD_APIKEY - Api key to interact with ipboard
|
||||
# IPBOARD_APIMODULE - Module for alliance auth *leave alone*
|
||||
#####################
|
||||
IPBOARD_ENDPOINT = os.environ.get('AA_IPBOARD_ENDPOINT', 'example.com/interface/board/index.php')
|
||||
IPBOARD_APIKEY = os.environ.get('AA_IPBOARD_APIKEY', 'somekeyhere')
|
||||
IPBOARD_APIMODULE = 'aa'
|
||||
|
||||
########################
|
||||
# XenForo Configuration
|
||||
########################
|
||||
XENFORO_ENDPOINT = os.environ.get('AA_XENFORO_ENDPOINT', 'example.com/api.php')
|
||||
XENFORO_DEFAULT_GROUP = os.environ.get('AA_XENFORO_DEFAULT_GROUP', 0)
|
||||
XENFORO_APIKEY = os.environ.get('AA_XENFORO_APIKEY', 'yourapikey')
|
||||
#####################
|
||||
|
||||
######################
|
||||
# Jabber Configuration
|
||||
######################
|
||||
# JABBER_URL - Jabber address url
|
||||
# JABBER_PORT - Jabber service portal
|
||||
# JABBER_SERVER - Jabber server url
|
||||
# OPENFIRE_ADDRESS - Address of the openfire admin console including port
|
||||
# Please use http with 9090 or https with 9091
|
||||
# OPENFIRE_SECRET_KEY - Openfire REST API secret key
|
||||
# BROADCAST_USER - Broadcast user JID
|
||||
# BROADCAST_USER_PASSWORD - Broadcast user password
|
||||
######################
|
||||
JABBER_URL = os.environ.get('AA_JABBER_URL', "example.com")
|
||||
JABBER_PORT = int(os.environ.get('AA_JABBER_PORT', '5223'))
|
||||
JABBER_SERVER = os.environ.get('AA_JABBER_SERVER', "example.com")
|
||||
OPENFIRE_ADDRESS = os.environ.get('AA_OPENFIRE_ADDRESS', "http://example.com:9090")
|
||||
OPENFIRE_SECRET_KEY = os.environ.get('AA_OPENFIRE_SECRET_KEY', "somekey")
|
||||
BROADCAST_USER = os.environ.get('AA_BROADCAST_USER', "broadcast@") + JABBER_URL
|
||||
BROADCAST_USER_PASSWORD = os.environ.get('AA_BROADCAST_USER_PASSWORD', "somepassword")
|
||||
BROADCAST_SERVICE_NAME = os.environ.get('AA_BROADCAST_SERVICE_NAME', "broadcast")
|
||||
|
||||
######################################
|
||||
# Mumble Configuration
|
||||
######################################
|
||||
# MUMBLE_URL - Mumble server url
|
||||
# MUMBLE_SERVER_ID - Mumble server id
|
||||
######################################
|
||||
MUMBLE_URL = os.environ.get('AA_MUMBLE_URL', "example.com")
|
||||
MUMBLE_SERVER_ID = int(os.environ.get('AA_MUMBLE_SERVER_ID', '1'))
|
||||
|
||||
######################################
|
||||
# PHPBB3 Configuration
|
||||
######################################
|
||||
|
||||
######################################
|
||||
# Teamspeak3 Configuration
|
||||
######################################
|
||||
# TEAMSPEAK3_SERVER_IP - Teamspeak3 server ip
|
||||
# TEAMSPEAK3_SERVER_PORT - Teamspeak3 server port
|
||||
# TEAMSPEAK3_SERVERQUERY_USER - Teamspeak3 serverquery username
|
||||
# TEAMSPEAK3_SERVERQUERY_PASSWORD - Teamspeak3 serverquery password
|
||||
# TEAMSPEAK3_VIRTUAL_SERVER - Virtual server id
|
||||
# TEAMSPEAK3_AUTHED_GROUP_ID - Default authed group id
|
||||
# TEAMSPEAK3_PUBLIC_URL - teamspeak3 public url used for link creation
|
||||
######################################
|
||||
TEAMSPEAK3_SERVER_IP = os.environ.get('AA_TEAMSPEAK3_SERVER_IP', '127.0.0.1')
|
||||
TEAMSPEAK3_SERVER_PORT = int(os.environ.get('AA_TEAMSPEAK3_SERVER_PORT', '10011'))
|
||||
TEAMSPEAK3_SERVERQUERY_USER = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_USER', 'serveradmin')
|
||||
TEAMSPEAK3_SERVERQUERY_PASSWORD = os.environ.get('AA_TEAMSPEAK3_SERVERQUERY_PASSWORD', 'passwordhere')
|
||||
TEAMSPEAK3_VIRTUAL_SERVER = int(os.environ.get('AA_TEAMSPEAK3_VIRTUAL_SERVER', '1'))
|
||||
TEAMSPEAK3_PUBLIC_URL = os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'example.com')
|
||||
|
||||
######################################
|
||||
# Discord Configuration
|
||||
######################################
|
||||
# DISCORD_GUILD_ID - ID of the guild to manage
|
||||
# DISCORD_BOT_TOKEN - oauth token of the app bot user
|
||||
# DISCORD_INVITE_CODE - invite code to the server
|
||||
# DISCORD_APP_ID - oauth app client ID
|
||||
# DISCORD_APP_SECRET - oauth app secret
|
||||
# DISCORD_CALLBACK_URL - oauth callback url
|
||||
# DISCORD_SYNC_NAMES - enable to force discord nicknames to be set to eve char name (bot needs Manage Nicknames permission)
|
||||
######################################
|
||||
DISCORD_GUILD_ID = os.environ.get('AA_DISCORD_GUILD_ID', '0118999')
|
||||
DISCORD_BOT_TOKEN = os.environ.get('AA_DISCORD_BOT_TOKEN', 'bottoken')
|
||||
DISCORD_INVITE_CODE = os.environ.get('AA_DISCORD_INVITE_CODE', 'invitecode')
|
||||
DISCORD_APP_ID = os.environ.get('AA_DISCORD_APP_ID', 'appid')
|
||||
DISCORD_APP_SECRET = os.environ.get('AA_DISCORD_APP_SECRET', 'secret')
|
||||
DISCORD_CALLBACK_URL = os.environ.get('AA_DISCORD_CALLBACK_URL', 'http://example.com/discord/callback')
|
||||
DISCORD_SYNC_NAMES = 'True' == os.environ.get('AA_DISCORD_SYNC_NAMES', 'False')
|
||||
|
||||
######################################
|
||||
# Discourse Configuration
|
||||
######################################
|
||||
# DISCOURSE_URL - Web address of the forums (no trailing slash)
|
||||
# DISCOURSE_API_USERNAME - API account username
|
||||
# DISCOURSE_API_KEY - API Key
|
||||
# DISCOURSE_SSO_SECRET - SSO secret key
|
||||
######################################
|
||||
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', 'https://example.com')
|
||||
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
|
||||
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
|
||||
DISCOURSE_SSO_SECRET = 'd836444a9e4084d5b224a60c208dce14'
|
||||
# Example secret from https://meta.discourse.org/t/official-single-sign-on-for-discourse/13045
|
||||
|
||||
#####################################
|
||||
# IPS4 Configuration
|
||||
#####################################
|
||||
# IPS4_URL - base url of the IPS4 install (no trailing slash)
|
||||
# IPS4_API_KEY - API key provided by IPS4
|
||||
#####################################
|
||||
IPS4_URL = os.environ.get('AA_IPS4_URL', 'http://example.com/ips4')
|
||||
IPS4_API_KEY = os.environ.get('AA_IPS4_API_KEY', '')
|
||||
|
||||
#####################################
|
||||
# SEAT Configuration
|
||||
#####################################
|
||||
# SEAT_URL - base url of the seat install (no trailing slash)
|
||||
# SEAT_XTOKEN - API key X-Token provided by SeAT
|
||||
#####################################
|
||||
SEAT_URL = os.environ.get('AA_SEAT_URL', 'http://example.com/seat')
|
||||
SEAT_XTOKEN = os.environ.get('AA_SEAT_XTOKEN', 'tokentokentoken')
|
||||
|
||||
######################################
|
||||
# SMF Configuration
|
||||
######################################
|
||||
SMF_URL = os.environ.get('AA_SMF_URL', '')
|
||||
|
||||
######################################
|
||||
# Fleet-Up Configuration
|
||||
######################################
|
||||
# FLEETUP_APP_KEY - The app key from http://fleet-up.com/Api/MyApps
|
||||
# FLEETUP_USER_ID - The user id from http://fleet-up.com/Api/MyKeys
|
||||
# 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
|
||||
######################################
|
||||
FLEETUP_APP_KEY = os.environ.get('AA_FLEETUP_APP_KEY', '')
|
||||
FLEETUP_USER_ID = os.environ.get('AA_FLEETUP_USER_ID', '')
|
||||
FLEETUP_API_ID = os.environ.get('AA_FLEETUP_API_ID', '')
|
||||
FLEETUP_GROUP_ID = os.environ.get('AA_FLEETUP_GROUP_ID', '')
|
||||
|
||||
PASSWORD_HASHERS = [
|
||||
'django.contrib.auth.hashers.MD5PasswordHasher',
|
||||
]
|
||||
|
||||
|
||||
LOGGING = {
|
||||
'version': 1,
|
||||
'disable_existing_loggers': False,
|
||||
'formatters': {
|
||||
'verbose': {
|
||||
'format' : "[%(asctime)s] %(levelname)s [%(name)s:%(lineno)s] %(message)s",
|
||||
'datefmt' : "%d/%b/%Y %H:%M:%S"
|
||||
},
|
||||
'simple': {
|
||||
'format': '%(levelname)s %(message)s'
|
||||
},
|
||||
},
|
||||
'handlers': {
|
||||
'console': {
|
||||
'level': 'DEBUG', # edit this line to change logging level to console
|
||||
'class': 'logging.StreamHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
'notifications': { # creates notifications for users with logging_notifications permission
|
||||
'level': 'ERROR', # edit this line to change logging level to notifications
|
||||
'class': 'notifications.handlers.NotificationHandler',
|
||||
'formatter': 'verbose',
|
||||
},
|
||||
},
|
||||
'loggers': {
|
||||
'authentication': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'celerytask': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'eveonline': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'groupmanagement': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'hrapplications': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'portal': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'registration': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'services': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'srp': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'timerboard': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'sigtracker': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'optimer': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'corputils': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'fleetactivitytracking': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
'util': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'DEBUG',
|
||||
},
|
||||
'django': {
|
||||
'handlers': ['console', 'notifications'],
|
||||
'level': 'ERROR',
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
LOGGING = None # Comment out to enable logging for debugging
|
||||
@@ -11,16 +11,19 @@ import groupmanagement.views
|
||||
import optimer.views
|
||||
import timerboard.views
|
||||
import fleetactivitytracking.views
|
||||
import fleetup.views
|
||||
import fleetup.urls
|
||||
import srp.views
|
||||
import notifications.views
|
||||
import hrapplications.views
|
||||
import corputils.urls
|
||||
import esi.urls
|
||||
import permissions_tool.urls
|
||||
from alliance_auth import NAME
|
||||
|
||||
admin.site.site_header = NAME
|
||||
|
||||
from alliance_auth.hooks import get_hooks
|
||||
|
||||
# Functional/Untranslated URL's
|
||||
urlpatterns = [
|
||||
# Locale
|
||||
@@ -33,9 +36,6 @@ urlpatterns = [
|
||||
url(r'^sso/', include(esi.urls, namespace='esi')),
|
||||
url(r'^sso/login$', authentication.views.sso_login, name='auth_sso_login'),
|
||||
|
||||
# Corputils
|
||||
url(r'^corpstats/', include(corputils.urls, namespace='corputils')),
|
||||
|
||||
# Index
|
||||
url(_(r'^$'), authentication.views.index_view, name='auth_index'),
|
||||
|
||||
@@ -47,85 +47,6 @@ urlpatterns = [
|
||||
name='auth_main_character_change'),
|
||||
url(r'^api_verify_owner/(\w+)/$', eveonline.views.api_sso_validate, name='auth_api_sso'),
|
||||
|
||||
# Forum Service Control
|
||||
url(r'^activate_forum/$', services.views.activate_forum, name='auth_activate_forum'),
|
||||
url(r'^deactivate_forum/$', services.views.deactivate_forum, name='auth_deactivate_forum'),
|
||||
url(r'^reset_forum_password/$', services.views.reset_forum_password,
|
||||
name='auth_reset_forum_password'),
|
||||
url(r'^set_forum_password/$', services.views.set_forum_password, name='auth_set_forum_password'),
|
||||
|
||||
# Jabber Service Control
|
||||
url(r'^activate_jabber/$', services.views.activate_jabber, name='auth_activate_jabber'),
|
||||
url(r'^deactivate_jabber/$', services.views.deactivate_jabber, name='auth_deactivate_jabber'),
|
||||
url(r'^reset_jabber_password/$', services.views.reset_jabber_password,
|
||||
name='auth_reset_jabber_password'),
|
||||
|
||||
# Mumble service control
|
||||
url(r'^activate_mumble/$', services.views.activate_mumble, name='auth_activate_mumble'),
|
||||
url(r'^deactivate_mumble/$', services.views.deactivate_mumble, name='auth_deactivate_mumble'),
|
||||
url(r'^reset_mumble_password/$', services.views.reset_mumble_password,
|
||||
name='auth_reset_mumble_password'),
|
||||
url(r'^set_mumble_password/$', services.views.set_mumble_password, name='auth_set_mumble_password'),
|
||||
|
||||
# Ipboard service control
|
||||
url(r'^activate_ipboard/$', services.views.activate_ipboard_forum,
|
||||
name='auth_activate_ipboard'),
|
||||
url(r'^deactivate_ipboard/$', services.views.deactivate_ipboard_forum,
|
||||
name='auth_deactivate_ipboard'),
|
||||
url(r'^reset_ipboard_password/$', services.views.reset_ipboard_password,
|
||||
name='auth_reset_ipboard_password'),
|
||||
url(r'^set_ipboard_password/$', services.views.set_ipboard_password, name='auth_set_ipboard_password'),
|
||||
|
||||
# XenForo service control
|
||||
url(r'^activate_xenforo/$', services.views.activate_xenforo_forum,
|
||||
name='auth_activate_xenforo'),
|
||||
url(r'^deactivate_xenforo/$', services.views.deactivate_xenforo_forum,
|
||||
name='auth_deactivate_xenforo'),
|
||||
url(r'^reset_xenforo_password/$', services.views.reset_xenforo_password,
|
||||
name='auth_reset_xenforo_password'),
|
||||
url(r'^set_xenforo_password/$', services.views.set_xenforo_password, name='auth_set_xenforo_password'),
|
||||
|
||||
# Teamspeak3 service control
|
||||
url(r'^activate_teamspeak3/$', services.views.activate_teamspeak3,
|
||||
name='auth_activate_teamspeak3'),
|
||||
url(r'^deactivate_teamspeak3/$', services.views.deactivate_teamspeak3,
|
||||
name='auth_deactivate_teamspeak3'),
|
||||
url(r'reset_teamspeak3_perm/$', services.views.reset_teamspeak3_perm,
|
||||
name='auth_reset_teamspeak3_perm'),
|
||||
|
||||
# Discord Service Control
|
||||
url(r'^activate_discord/$', services.views.activate_discord, name='auth_activate_discord'),
|
||||
url(r'^deactivate_discord/$', services.views.deactivate_discord, name='auth_deactivate_discord'),
|
||||
url(r'^reset_discord/$', services.views.reset_discord, name='auth_reset_discord'),
|
||||
url(r'^discord_callback/$', services.views.discord_callback, name='auth_discord_callback'),
|
||||
url(r'^discord_add_bot/$', services.views.discord_add_bot, name='auth_discord_add_bot'),
|
||||
|
||||
# Discourse Service Control
|
||||
url(r'^discourse_sso$', services.views.discourse_sso, name='auth_discourse_sso'),
|
||||
|
||||
# IPS4 Service Control
|
||||
url(r'^activate_ips4/$', services.views.activate_ips4,
|
||||
name='auth_activate_ips4'),
|
||||
url(r'^deactivate_ips4/$', services.views.deactivate_ips4,
|
||||
name='auth_deactivate_ips4'),
|
||||
url(r'^reset_ips4_password/$', services.views.reset_ips4_password,
|
||||
name='auth_reset_ips4_password'),
|
||||
url(r'^set_ips4_password/$', services.views.set_ips4_password, name='auth_set_ips4_password'),
|
||||
|
||||
# SMF Service Control
|
||||
url(r'^activate_smf/$', services.views.activate_smf, name='auth_activate_smf'),
|
||||
url(r'^deactivate_smf/$', services.views.deactivate_smf, name='auth_deactivate_smf'),
|
||||
url(r'^reset_smf_password/$', services.views.reset_smf_password,
|
||||
name='auth_reset_smf_password'),
|
||||
url(r'^set_smf_password/$', services.views.set_smf_password, name='auth_set_smf_password'),
|
||||
|
||||
# Alliance Market Control
|
||||
url(r'^activate_market/$', services.views.activate_market, name='auth_activate_market'),
|
||||
url(r'^deactivate_market/$', services.views.deactivate_market, name='auth_deactivate_market'),
|
||||
url(r'^reset_market_password/$', services.views.reset_market_password,
|
||||
name='auth_reset_market_password'),
|
||||
url(r'^set_market_password/$', services.views.set_market_password, name='auth_set_market_password'),
|
||||
|
||||
# SRP URLS
|
||||
url(r'^srp_fleet_remove/(\w+)$', srp.views.srp_fleet_remove, name='auth_srp_fleet_remove'),
|
||||
url(r'^srp_fleet_disable/(\w+)$', srp.views.srp_fleet_disable, name='auth_srp_fleet_disable'),
|
||||
@@ -134,11 +55,14 @@ urlpatterns = [
|
||||
name='auth_srp_fleet_mark_completed'),
|
||||
url(r'^srp_fleet_mark_uncompleted/(\w+)', srp.views.srp_fleet_mark_uncompleted,
|
||||
name='auth_srp_fleet_mark_uncompleted'),
|
||||
url(r'^srp_request_remove/(\w+)', srp.views.srp_request_remove,
|
||||
url(r'^srp_request_remove/', srp.views.srp_request_remove,
|
||||
name="auth_srp_request_remove"),
|
||||
url(r'srp_request_approve/(\w+)', srp.views.srp_request_approve,
|
||||
url(r'srp_request_approve/', srp.views.srp_request_approve,
|
||||
name='auth_srp_request_approve'),
|
||||
url(r'srp_request_reject/(\w+)', srp.views.srp_request_reject, name='auth_srp_request_reject'),
|
||||
url(r'srp_request_reject/', srp.views.srp_request_reject,
|
||||
name='auth_srp_request_reject'),
|
||||
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount,
|
||||
name="auth_srp_request_update_amount"),
|
||||
|
||||
# Notifications
|
||||
url(r'^remove_notifications/(\w+)/$', notifications.views.remove_notification, name='auth_remove_notification'),
|
||||
@@ -151,12 +75,7 @@ urlpatterns = [
|
||||
urlpatterns += i18n_patterns(
|
||||
|
||||
# Fleetup
|
||||
url(r'^fleetup/$', fleetup.views.fleetup_view, name='auth_fleetup_view'),
|
||||
url(r'^fleetup/fittings/$', fleetup.views.fleetup_fittings, name='auth_fleetup_fittings'),
|
||||
url(r'^fleetup/fittings/(?P<fittingnumber>[0-9]+)/$', fleetup.views.fleetup_fitting, name='auth_fleetup_fitting'),
|
||||
url(r'^fleetup/doctrines/$', fleetup.views.fleetup_doctrines, name='auth_fleetup_doctrines'),
|
||||
url(r'^fleetup/characters/$', fleetup.views.fleetup_characters, name='auth_fleetup_characters'),
|
||||
url(r'^fleetup/doctrines/(?P<doctrinenumber>[0-9]+)/$', fleetup.views.fleetup_doctrine, name='auth_fleetup_doctrine'),
|
||||
url(r'^fleetup/', include(fleetup.urls.urlpatterns)),
|
||||
|
||||
# Authentication
|
||||
url(_(r'^login_user/'), authentication.views.login_user, name='auth_login_user'),
|
||||
@@ -183,6 +102,9 @@ urlpatterns += i18n_patterns(
|
||||
url(_(r'^refresh_api_pair/([0-9]+)/$'), eveonline.views.user_refresh_api, name='auth_user_refresh_api'),
|
||||
url(_(r'^delete_api_pair/(\w+)/$'), eveonline.views.api_key_removal, name='auth_api_key_removal'),
|
||||
url(_(r'^characters/'), eveonline.views.characters_view, name='auth_characters'),
|
||||
|
||||
# Corputils
|
||||
url(_(r'^corpstats/'), include(corputils.urls, namespace='corputils')),
|
||||
|
||||
# Group management
|
||||
url(_(r'^groups/'), groupmanagement.views.groups_view, name='auth_groups'),
|
||||
@@ -241,11 +163,6 @@ urlpatterns += i18n_patterns(
|
||||
|
||||
# Service Urls
|
||||
url(_(r'^services/$'), services.views.services_view, name='auth_services'),
|
||||
url(_(r'^services/jabber_broadcast/$'), services.views.jabber_broadcast_view,
|
||||
name='auth_jabber_broadcast_view'),
|
||||
|
||||
# Teamspeak Urls
|
||||
url(r'verify_teamspeak3/$', services.views.verify_teamspeak3, name='auth_verify_teamspeak3'),
|
||||
|
||||
# Timer URLS
|
||||
url(_(r'^timers/$'), timerboard.views.timer_view, name='auth_timer_view'),
|
||||
@@ -260,8 +177,6 @@ urlpatterns += i18n_patterns(
|
||||
url(_(r'^srp_fleet_add_view/$'), srp.views.srp_fleet_add_view, name='auth_srp_fleet_add_view'),
|
||||
url(_(r'^srp_fleet_edit/(\w+)$'), srp.views.srp_fleet_edit_view, name='auth_srp_fleet_edit_view'),
|
||||
url(_(r'^srp_request/(\w+)'), srp.views.srp_request_view, name='auth_srp_request_view'),
|
||||
url(_(r'srp_request_amount_update/(\w+)'), srp.views.srp_request_update_amount_view,
|
||||
name="auth_srp_request_update_amount_view"),
|
||||
|
||||
# Tools
|
||||
url(_(r'^tool/fleet_formatter_tool/$'), services.views.fleet_formatter_view,
|
||||
@@ -271,12 +186,12 @@ urlpatterns += i18n_patterns(
|
||||
url(_(r'^notifications/$'), notifications.views.notification_list, name='auth_notification_list'),
|
||||
url(_(r'^notifications/(\w+)/$'), notifications.views.notification_view, name='auth_notification_view'),
|
||||
|
||||
# Jabber
|
||||
url(_(r'^set_jabber_password/$'), services.views.set_jabber_password, name='auth_set_jabber_password'),
|
||||
|
||||
# FleetActivityTracking (FAT)
|
||||
url(r'^fat/$', fleetactivitytracking.views.fatlink_view, name='auth_fatlink_view'),
|
||||
url(r'^fat/statistics/$', fleetactivitytracking.views.fatlink_statistics_view, name='auth_fatlink_view_statistics'),
|
||||
url(r'^fat/statistics/corp/(\w+)$', fleetactivitytracking.views.fatlink_statistics_corp_view, name='auth_fatlink_view_statistics_corp'),
|
||||
url(r'^fat/statistics/corp/(?P<corpid>\w+)/(?P<year>[0-9]+)/(?P<month>[0-9]+)/', fleetactivitytracking.views.fatlink_statistics_corp_view,
|
||||
name='auth_fatlink_view_statistics_corp_month'),
|
||||
url(r'^fat/statistics/(?P<year>[0-9]+)/(?P<month>[0-9]+)/$', fleetactivitytracking.views.fatlink_statistics_view,
|
||||
name='auth_fatlink_view_statistics_month'),
|
||||
url(r'^fat/user/statistics/$', fleetactivitytracking.views.fatlink_personal_statistics_view,
|
||||
@@ -296,4 +211,12 @@ urlpatterns += i18n_patterns(
|
||||
url(r'^fat/link/$', fleetactivitytracking.views.fatlink_view, name='auth_click_fatlink_view'),
|
||||
url(r'^fat/link/(?P<hash>[a-zA-Z0-9]+)/(?P<fatname>[a-z0-9_-]+)/$',
|
||||
fleetactivitytracking.views.click_fatlink_view),
|
||||
|
||||
url(r'^permissions/', include(permissions_tool.urls))
|
||||
)
|
||||
|
||||
# Append hooked service urls
|
||||
services = get_hooks('services_hook')
|
||||
for svc in services:
|
||||
urlpatterns += svc().urlpatterns
|
||||
|
||||
|
||||
@@ -1,22 +1,19 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.contrib import admin
|
||||
from django.contrib.auth.admin import UserAdmin as BaseUserAdmin
|
||||
from django.contrib.auth.models import User
|
||||
from django.utils.text import slugify
|
||||
|
||||
from authentication.models import AuthServicesInfo
|
||||
from eveonline.models import EveCharacter
|
||||
from services.tasks import update_jabber_groups
|
||||
from services.tasks import update_mumble_groups
|
||||
from services.tasks import update_forum_groups
|
||||
from services.tasks import update_ipboard_groups
|
||||
from services.tasks import update_smf_groups
|
||||
from services.tasks import update_teamspeak3_groups
|
||||
from services.tasks import update_discord_groups
|
||||
from services.tasks import update_discord_nickname
|
||||
from services.tasks import update_discourse_groups
|
||||
from alliance_auth.hooks import get_hooks
|
||||
from services.hooks import ServicesHook
|
||||
|
||||
|
||||
@admin.register(AuthServicesInfo)
|
||||
class AuthServicesInfoManager(admin.ModelAdmin):
|
||||
|
||||
@staticmethod
|
||||
def main_character(obj):
|
||||
if obj.main_char_id:
|
||||
@@ -34,120 +31,69 @@ class AuthServicesInfoManager(admin.ModelAdmin):
|
||||
def has_add_permission(request, obj=None):
|
||||
return False
|
||||
|
||||
def sync_jabber(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset: # queryset filtering doesn't work here?
|
||||
if a.jabber_username != "":
|
||||
update_jabber_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s jabber accounts queued for group sync." % count)
|
||||
|
||||
sync_jabber.short_description = "Sync groups for selected jabber accounts"
|
||||
|
||||
def sync_mumble(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.mumble_username != "":
|
||||
update_mumble_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s mumble accounts queued for group sync." % count)
|
||||
|
||||
sync_mumble.short_description = "Sync groups for selected mumble accounts"
|
||||
|
||||
def sync_forum(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.forum_username != "":
|
||||
update_forum_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s forum accounts queued for group sync." % count)
|
||||
|
||||
sync_forum.short_description = "Sync groups for selected forum accounts"
|
||||
|
||||
def sync_ipboard(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.ipboard_username != "":
|
||||
update_ipboard_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s ipboard accounts queued for group sync." % count)
|
||||
|
||||
sync_ipboard.short_description = "Sync groups for selected ipboard accounts"
|
||||
|
||||
def sync_smf(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.smf_username != "":
|
||||
update_smf_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s smf accounts queued for group sync." % count)
|
||||
|
||||
sync_smf.short_description = "Sync groups for selected smf accounts"
|
||||
|
||||
def sync_teamspeak(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.teamspeak3_uid != "":
|
||||
update_teamspeak3_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s teamspeak accounts queued for group sync." % count)
|
||||
|
||||
sync_teamspeak.short_description = "Sync groups for selected teamspeak accounts"
|
||||
|
||||
def sync_discord(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.discord_uid != "":
|
||||
update_discord_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s discord accounts queued for group sync." % count)
|
||||
|
||||
sync_discord.short_description = "Sync groups for selected discord accounts"
|
||||
|
||||
def sync_discourse(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.discourse_enabled:
|
||||
update_discourse_groups.delay(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s discourse accounts queued for group sync." % count)
|
||||
|
||||
sync_discourse.short_description = "Sync groups for selected discourse accounts"
|
||||
|
||||
def sync_nicknames(self, request, queryset):
|
||||
count = 0
|
||||
for a in queryset:
|
||||
if a.discord_uid != "":
|
||||
update_discord_nickname(a.user.pk)
|
||||
count += 1
|
||||
self.message_user(request, "%s discord accounts queued for nickname sync." % count)
|
||||
|
||||
sync_nicknames.short_description = "Sync nicknames for selected discord accounts"
|
||||
|
||||
actions = [
|
||||
'sync_jabber',
|
||||
'sync_mumble',
|
||||
'sync_forum',
|
||||
'sync_ipboard',
|
||||
'sync_smf',
|
||||
'sync_teamspeak',
|
||||
'sync_discord',
|
||||
'sync_discourse',
|
||||
'sync_nicknames',
|
||||
]
|
||||
|
||||
search_fields = [
|
||||
'user__username',
|
||||
'ipboard_username',
|
||||
'xenforo_username',
|
||||
'forum_username',
|
||||
'jabber_username',
|
||||
'mumble_username',
|
||||
'teamspeak3_uid',
|
||||
'discord_uid',
|
||||
'ips4_username',
|
||||
'smf_username',
|
||||
'market_username',
|
||||
'main_char_id',
|
||||
]
|
||||
list_display = ('user', 'main_character')
|
||||
|
||||
|
||||
def make_service_hooks_update_groups_action(service):
|
||||
"""
|
||||
Make a admin action for the given service
|
||||
:param service: services.hooks.ServicesHook
|
||||
:return: fn to update services groups for the selected users
|
||||
"""
|
||||
def update_service_groups(modeladmin, request, queryset):
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.update_groups(user)
|
||||
|
||||
update_service_groups.__name__ = str('update_{}_groups'.format(slugify(service.name)))
|
||||
update_service_groups.short_description = "Sync groups for selected {} accounts".format(service.title)
|
||||
return update_service_groups
|
||||
|
||||
|
||||
def make_service_hooks_sync_nickname_action(service):
|
||||
"""
|
||||
Make a sync_nickname admin action for the given service
|
||||
:param service: services.hooks.ServicesHook
|
||||
:return: fn to sync nickname for the selected users
|
||||
"""
|
||||
def sync_nickname(modeladmin, request, queryset):
|
||||
for user in queryset: # queryset filtering doesn't work here?
|
||||
service.sync_nickname(user)
|
||||
|
||||
sync_nickname.__name__ = str('sync_{}_nickname'.format(slugify(service.name)))
|
||||
sync_nickname.short_description = "Sync nicknames for selected {} accounts".format(service.title)
|
||||
return sync_nickname
|
||||
|
||||
|
||||
class UserAdmin(BaseUserAdmin):
|
||||
"""
|
||||
Extending Django's UserAdmin model
|
||||
"""
|
||||
def get_actions(self, request):
|
||||
actions = super(BaseUserAdmin, self).get_actions(request)
|
||||
|
||||
for hook in get_hooks('services_hook'):
|
||||
svc = hook()
|
||||
# Check update_groups is redefined/overloaded
|
||||
if svc.update_groups.__module__ != ServicesHook.update_groups.__module__:
|
||||
action = make_service_hooks_update_groups_action(svc)
|
||||
actions[action.__name__] = (action,
|
||||
action.__name__,
|
||||
action.short_description)
|
||||
# Create sync nickname action if service implements it
|
||||
if svc.sync_nickname.__module__ != ServicesHook.sync_nickname.__module__:
|
||||
action = make_service_hooks_sync_nickname_action(svc)
|
||||
actions[action.__name__] = (action,
|
||||
action.__name__,
|
||||
action.short_description)
|
||||
|
||||
return actions
|
||||
|
||||
# Re-register UserAdmin
|
||||
try:
|
||||
admin.site.unregister(User)
|
||||
finally:
|
||||
admin.site.register(User, UserAdmin)
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import unicode_literals
|
||||
from django import forms
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.contrib.auth.models import User
|
||||
from django.conf import settings
|
||||
import re
|
||||
|
||||
|
||||
@@ -9,6 +10,10 @@ class LoginForm(forms.Form):
|
||||
username = forms.CharField(label=_('Username'), max_length=32, required=True)
|
||||
password = forms.CharField(label=_('Password'), widget=forms.PasswordInput())
|
||||
|
||||
if getattr(settings, 'CAPTCHA_ENABLED', False):
|
||||
from captcha.fields import ReCaptchaField
|
||||
captcha = ReCaptchaField()
|
||||
|
||||
|
||||
class RegistrationForm(forms.Form):
|
||||
username = forms.CharField(label=_('Username'), max_length=30, required=True)
|
||||
@@ -17,16 +22,16 @@ class RegistrationForm(forms.Form):
|
||||
email = forms.CharField(label=_('Email'), max_length=254, required=True)
|
||||
email_again = forms.CharField(label=_('Email Again'), max_length=254, required=True)
|
||||
|
||||
if getattr(settings, 'CAPTCHA_ENABLED', False):
|
||||
from captcha.fields import ReCaptchaField
|
||||
captcha = ReCaptchaField()
|
||||
|
||||
def clean(self):
|
||||
if ' ' in self.cleaned_data['username']:
|
||||
raise forms.ValidationError('Username cannot contain a space')
|
||||
|
||||
# We attempt to get the user object if we succeed we know email as been used
|
||||
try:
|
||||
User.objects.get(email=self.cleaned_data['email'])
|
||||
if User.objects.filter(email=self.cleaned_data['email']).count() >= 1:
|
||||
raise forms.ValidationError('Email as already been used')
|
||||
except User.DoesNotExist:
|
||||
pass
|
||||
|
||||
if not re.match("^\w+$", self.cleaned_data['username']):
|
||||
raise forms.ValidationError('Username contains illegal characters')
|
||||
|
||||
@@ -24,73 +24,6 @@ class AuthServicesInfoManager:
|
||||
else:
|
||||
logger.error("Failed to update user %s main character id to %s: user does not exist." % (user, char_id))
|
||||
|
||||
@staticmethod
|
||||
def update_user_forum_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s forum info: username %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.forum_username = username
|
||||
authserviceinfo.save(update_fields=['forum_username'])
|
||||
logger.info("Updated user %s forum info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s forum info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_jabber_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s jabber info: username %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.jabber_username = username
|
||||
authserviceinfo.save(update_fields=['jabber_username'])
|
||||
logger.info("Updated user %s jabber info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s jabber info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_mumble_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s mumble info: username %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.mumble_username = username
|
||||
authserviceinfo.save(update_fields=['mumble_username'])
|
||||
logger.info("Updated user %s mumble info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s mumble info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_ipboard_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s ipboard info: uername %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.ipboard_username = username
|
||||
authserviceinfo.save(update_fields=['ipboard_username'])
|
||||
logger.info("Updated user %s ipboard info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s ipboard info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_xenforo_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s xenforo info: uername %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.xenforo_username = username
|
||||
authserviceinfo.save(update_fields=['xenforo_username'])
|
||||
logger.info("Updated user %s xenforo info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s xenforo info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_teamspeak3_info(uid, perm_key, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s teamspeak3 info: uid %s" % (user, uid))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.teamspeak3_uid = uid
|
||||
authserviceinfo.teamspeak3_perm_key = perm_key
|
||||
authserviceinfo.save(update_fields=['teamspeak3_uid', 'teamspeak3_perm_key'])
|
||||
logger.info("Updated user %s teamspeak3 info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s teamspeak3 info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_is_blue(is_blue, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
@@ -100,51 +33,6 @@ class AuthServicesInfoManager:
|
||||
authserviceinfo.save(update_fields=['is_blue'])
|
||||
logger.info("Updated user %s blue status to %s in authservicesinfo model." % (user, is_blue))
|
||||
|
||||
@staticmethod
|
||||
def update_user_discord_info(user_id, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s discord info: user_id %s" % (user, user_id))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.discord_uid = user_id
|
||||
authserviceinfo.save(update_fields=['discord_uid'])
|
||||
logger.info("Updated user %s discord info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s discord info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_ips4_info(username, id, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s IPS4 info: username %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.ips4_username = username
|
||||
authserviceinfo.ips4_id = id
|
||||
authserviceinfo.save(update_fields=['ips4_username', 'ips4_id'])
|
||||
logger.info("Updated user %s IPS4 info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s IPS4 info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_smf_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s forum info: username %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.smf_username = username
|
||||
authserviceinfo.save(update_fields=['smf_username'])
|
||||
logger.info("Updated user %s smf info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s smf info: user does not exist." % user)
|
||||
|
||||
@staticmethod
|
||||
def update_user_market_info(username, user):
|
||||
if User.objects.filter(username=user.username).exists():
|
||||
logger.debug("Updating user %s market info: username %s" % (user, username))
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
authserviceinfo.market_username = username
|
||||
authserviceinfo.save(update_fields=['market_username'])
|
||||
logger.info("Updated user %s market info in authservicesinfo model." % user)
|
||||
else:
|
||||
logger.error("Failed to update user %s market info: user does not exist." % user)
|
||||
|
||||
|
||||
class UserState:
|
||||
def __init__(self):
|
||||
|
||||
323
authentication/migrations/0013_service_modules.py
Normal file
@@ -0,0 +1,323 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.2 on 2016-12-11 23:14
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations
|
||||
from django.conf import settings
|
||||
from django.core.exceptions import ObjectDoesNotExist
|
||||
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def optional_dependencies():
|
||||
"""
|
||||
Only require these migrations if the given app
|
||||
is installed. If the app isn't installed then
|
||||
the relevant AuthServicesInfo field will be LOST
|
||||
when the data migration is run.
|
||||
"""
|
||||
installed_apps = settings.INSTALLED_APPS
|
||||
|
||||
dependencies = []
|
||||
|
||||
# Skip adding module dependencies if the settings specifies that services have been migrated
|
||||
if hasattr(settings, 'SERVICES_MIGRATED') and settings.SERVICES_MIGRATED:
|
||||
return dependencies
|
||||
|
||||
if 'services.modules.xenforo' in installed_apps:
|
||||
dependencies.append(('xenforo', '0001_initial'))
|
||||
if 'services.modules.discord' in installed_apps:
|
||||
dependencies.append(('discord', '0001_initial'))
|
||||
if 'services.modules.discourse' in installed_apps:
|
||||
dependencies.append(('discourse', '0001_initial'))
|
||||
if 'services.modules.ipboard' in installed_apps:
|
||||
dependencies.append(('ipboard', '0001_initial'))
|
||||
if 'services.modules.ips4' in installed_apps:
|
||||
dependencies.append(('ips4', '0001_initial'))
|
||||
if 'services.modules.market' in installed_apps:
|
||||
dependencies.append(('market', '0001_initial'))
|
||||
if 'services.modules.openfire' in installed_apps:
|
||||
dependencies.append(('openfire', '0001_initial'))
|
||||
if 'services.modules.smf' in installed_apps:
|
||||
dependencies.append(('smf', '0001_initial'))
|
||||
if 'services.modules.teamspeak3' in installed_apps:
|
||||
dependencies.append(('teamspeak3', '0003_teamspeak3user'))
|
||||
if 'services.modules.mumble' in installed_apps:
|
||||
dependencies.append(('mumble', '0003_mumbleuser_user'))
|
||||
if 'services.modules.phpbb3' in installed_apps:
|
||||
dependencies.append(('phpbb3', '0001_initial'))
|
||||
|
||||
return dependencies
|
||||
|
||||
|
||||
class DatalossException(Exception):
|
||||
def __init__(self, field, app):
|
||||
message = "This migration would cause a loss of data for the %s field, ensure the %s app is installed and" \
|
||||
" run the migration again" % (field, app)
|
||||
super(Exception, self).__init__(message)
|
||||
|
||||
|
||||
def check_for_dataloss(authservicesinfo):
|
||||
"""
|
||||
Check if any of the authservicesinfo contain a field which contains data
|
||||
that would be lost because the target module for migration is not installed.
|
||||
If a record is found with no matching target installed an exception is raised.
|
||||
:param authservicesinfo: AuthServicesInfo records to check
|
||||
"""
|
||||
installed_apps = settings.INSTALLED_APPS
|
||||
|
||||
for authinfo in authservicesinfo:
|
||||
if authinfo.xenforo_username and 'services.modules.xenforo' not in installed_apps:
|
||||
raise DatalossException('xenforo_username', 'services.modules.xenforo')
|
||||
if authinfo.discord_uid and 'services.modules.discord' not in installed_apps:
|
||||
raise DatalossException('discord_uid', 'services.modules.discord')
|
||||
if authinfo.discourse_enabled and 'services.modules.discourse' not in installed_apps:
|
||||
raise DatalossException('discourse_enabled', 'services.modules.discourse')
|
||||
if authinfo.ipboard_username and 'services.modules.ipboard' not in installed_apps:
|
||||
raise DatalossException('ipboard_username', 'services.modules.ipboard')
|
||||
if authinfo.ips4_id and 'services.modules.ips4' not in installed_apps:
|
||||
raise DatalossException('ips4_id', 'services.modules.ips4')
|
||||
if authinfo.market_username and 'services.modules.market' not in installed_apps:
|
||||
raise DatalossException('market_username', 'services.modules.market')
|
||||
if authinfo.jabber_username and 'services.modules.openfire' not in installed_apps:
|
||||
raise DatalossException('jabber_username', 'services.modules.openfire')
|
||||
if authinfo.smf_username and 'services.modules.smf' not in installed_apps:
|
||||
raise DatalossException('smf_username', 'services.modules.smf')
|
||||
if authinfo.teamspeak3_uid and 'services.modules.teamspeak3' not in installed_apps:
|
||||
raise DatalossException('teamspeak3_uid', 'services.modules.teamspeak3')
|
||||
if authinfo.mumble_username and 'services.modules.mumble' not in installed_apps:
|
||||
raise DatalossException('mumble_username', 'services.modules.mumble')
|
||||
if authinfo.forum_username and 'services.modules.phpbb3' not in installed_apps:
|
||||
raise DatalossException('forum_username', 'services.modules.phpbb3')
|
||||
|
||||
|
||||
def forward(apps, schema_editor):
|
||||
installed_apps = settings.INSTALLED_APPS
|
||||
AuthServicesInfo = apps.get_model("authentication", "AuthServicesInfo")
|
||||
|
||||
# Check if any records would result in dataloss
|
||||
check_for_dataloss(AuthServicesInfo.objects.all())
|
||||
|
||||
XenforoUser = apps.get_model('xenforo', 'XenforoUser') if 'services.modules.xenforo' in installed_apps else None
|
||||
DiscordUser = apps.get_model('discord', 'DiscordUser') if 'services.modules.discord' in installed_apps else None
|
||||
DiscourseUser = apps.get_model('discourse', 'DiscourseUser') if 'services.modules.discourse' in installed_apps else None
|
||||
IpboardUser = apps.get_model('ipboard', 'IpboardUser') if 'services.modules.ipboard' in installed_apps else None
|
||||
Ips4User = apps.get_model('ips4', 'Ips4User') if 'services.modules.ips4' in installed_apps else None
|
||||
MarketUser = apps.get_model('market', 'MarketUser') if 'services.modules.market' in installed_apps else None
|
||||
OpenfireUser = apps.get_model('openfire', 'OpenfireUser') if 'services.modules.openfire' in installed_apps else None
|
||||
SmfUser = apps.get_model('smf', 'SmfUser') if 'services.modules.smf' in installed_apps else None
|
||||
Teamspeak3User = apps.get_model('teamspeak3', 'Teamspeak3User') if 'services.modules.teamspeak3' in installed_apps else None
|
||||
MumbleUser = apps.get_model('mumble', 'MumbleUser') if 'services.modules.mumble' in installed_apps else None
|
||||
Phpbb3User = apps.get_model('phpbb3', 'Phpbb3User') if 'services.modules.phpbb3' in installed_apps else None
|
||||
|
||||
for authinfo in AuthServicesInfo.objects.all():
|
||||
user = authinfo.user
|
||||
|
||||
if XenforoUser is not None and authinfo.xenforo_username:
|
||||
logging.debug('Updating Xenforo info for %s' % user.username)
|
||||
xfu = XenforoUser()
|
||||
xfu.user = user
|
||||
xfu.username = authinfo.xenforo_username
|
||||
xfu.save()
|
||||
|
||||
if DiscordUser is not None and authinfo.discord_uid:
|
||||
logging.debug('Updating Discord info for %s' % user.username)
|
||||
du = DiscordUser()
|
||||
du.user = user
|
||||
du.uid = authinfo.discord_uid
|
||||
du.save()
|
||||
|
||||
if DiscourseUser is not None and authinfo.discourse_enabled:
|
||||
logging.debug('Updating Discourse info for %s' % user.username)
|
||||
du = DiscourseUser()
|
||||
du.user = user
|
||||
du.enabled = authinfo.discourse_enabled
|
||||
du.save()
|
||||
|
||||
if IpboardUser is not None and authinfo.ipboard_username:
|
||||
logging.debug('Updating IPBoard info for %s' % user.username)
|
||||
ipb = IpboardUser()
|
||||
ipb.user = user
|
||||
ipb.username = authinfo.ipboard_username
|
||||
ipb.save()
|
||||
|
||||
if Ips4User is not None and authinfo.ips4_id:
|
||||
logging.debug('Updating Ips4 info for %s' % user.username)
|
||||
ips = Ips4User()
|
||||
ips.user = user
|
||||
ips.id = authinfo.ips4_id
|
||||
ips.username = authinfo.ips4_username
|
||||
ips.save()
|
||||
|
||||
if MarketUser is not None and authinfo.market_username:
|
||||
logging.debug('Updating Market info for %s' % user.username)
|
||||
mkt = MarketUser()
|
||||
mkt.user = user
|
||||
mkt.username = authinfo.market_username
|
||||
mkt.save()
|
||||
|
||||
if OpenfireUser is not None and authinfo.jabber_username:
|
||||
logging.debug('Updating Openfire (jabber) info for %s' % user.username)
|
||||
ofu = OpenfireUser()
|
||||
ofu.user = user
|
||||
ofu.username = authinfo.jabber_username
|
||||
ofu.save()
|
||||
|
||||
if SmfUser is not None and authinfo.smf_username:
|
||||
logging.debug('Updating SMF info for %s' % user.username)
|
||||
smf = SmfUser()
|
||||
smf.user = user
|
||||
smf.username = authinfo.smf_username
|
||||
smf.save()
|
||||
|
||||
if Teamspeak3User is not None and authinfo.teamspeak3_uid:
|
||||
logging.debug('Updating Teamspeak3 info for %s' % user.username)
|
||||
ts3 = Teamspeak3User()
|
||||
ts3.user = user
|
||||
ts3.uid = authinfo.teamspeak3_uid
|
||||
ts3.perm_key = authinfo.teamspeak3_perm_key
|
||||
ts3.save()
|
||||
|
||||
if MumbleUser is not None and authinfo.mumble_username:
|
||||
logging.debug('Updating mumble info for %s' % user.username)
|
||||
try:
|
||||
mbl = MumbleUser.objects.get(username=authinfo.mumble_username)
|
||||
mbl.user = user
|
||||
mbl.save()
|
||||
except ObjectDoesNotExist:
|
||||
logger.warn('AuthServiceInfo mumble_username for {} but no '
|
||||
'corresponding record in MumbleUser, dropping'.format(user.username))
|
||||
|
||||
if Phpbb3User is not None and authinfo.forum_username:
|
||||
logging.debug('Updating phpbb3 info for %s' % user.username)
|
||||
phb = Phpbb3User()
|
||||
phb.user = user
|
||||
phb.username = authinfo.forum_username
|
||||
phb.save()
|
||||
|
||||
|
||||
def reverse(apps, schema_editor):
|
||||
User = apps.get_model('auth', 'User')
|
||||
AuthServicesInfo = apps.get_model("authentication", "AuthServicesInfo")
|
||||
|
||||
for user in User.objects.all():
|
||||
authinfo, c = AuthServicesInfo.objects.get_or_create(user=user)
|
||||
|
||||
if hasattr(user, 'xenforo'):
|
||||
logging.debug('Reversing xenforo for %s' % user.username)
|
||||
authinfo.xenforo_username = user.xenforo.username
|
||||
|
||||
if hasattr(user, 'discord'):
|
||||
logging.debug('Reversing discord for %s' % user.username)
|
||||
authinfo.discord_uid = user.discord.uid
|
||||
|
||||
if hasattr(user, 'discourse'):
|
||||
logging.debug('Reversing discourse for %s' % user.username)
|
||||
authinfo.discourse_enabled = user.discourse.enabled
|
||||
|
||||
if hasattr(user, 'ipboard'):
|
||||
logging.debug('Reversing ipboard for %s' % user.username)
|
||||
authinfo.ipboard_username = user.ipboard.username
|
||||
|
||||
if hasattr(user, 'ips4'):
|
||||
logging.debug('Reversing ips4 for %s' % user.username)
|
||||
authinfo.ips4_id = user.ips4.id
|
||||
authinfo.ips4_username = user.ips4.username
|
||||
|
||||
if hasattr(user, 'market'):
|
||||
logging.debug('Reversing market for %s' % user.username)
|
||||
authinfo.market_username = user.market.username
|
||||
|
||||
if hasattr(user, 'openfire'):
|
||||
logging.debug('Reversing openfire (jabber) for %s' % user.username)
|
||||
authinfo.jabber_username = user.openfire.username
|
||||
|
||||
if hasattr(user, 'smf'):
|
||||
logging.debug('Reversing smf for %s' % user.username)
|
||||
authinfo.smf_username = user.smf.username
|
||||
|
||||
if hasattr(user, 'teamspeak3'):
|
||||
logging.debug('Reversing teamspeak3 for %s' % user.username)
|
||||
authinfo.teamspeak3_uid = user.teamspeak3.uid
|
||||
authinfo.teamspeak3_perm_key = user.teamspeak3.perm_key
|
||||
|
||||
if hasattr(user, 'mumble'):
|
||||
logging.debug('Reversing mumble for %s' % user.username)
|
||||
try:
|
||||
authinfo.mumble_username = user.mumble.all()[:1].get().username
|
||||
except ObjectDoesNotExist:
|
||||
logging.debug('Failed to reverse mumble for %s' % user.username)
|
||||
|
||||
if hasattr(user, 'phpbb3'):
|
||||
logging.debug('Reversing phpbb3 for %s' % user.username)
|
||||
authinfo.forum_username = user.phpbb3.username
|
||||
|
||||
logging.debug('Saving AuthServicesInfo for %s ' % user.username)
|
||||
authinfo.save()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = optional_dependencies() + [
|
||||
('authentication', '0012_remove_add_delete_authservicesinfo_permissions'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
# Migrate data
|
||||
migrations.RunPython(forward, reverse),
|
||||
# Remove fields
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='discord_uid',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='discourse_enabled',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='forum_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='ipboard_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='ips4_id',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='ips4_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='jabber_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='market_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='mumble_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='smf_username',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='teamspeak3_perm_key',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='teamspeak3_uid',
|
||||
),
|
||||
migrations.RemoveField(
|
||||
model_name='authservicesinfo',
|
||||
name='xenforo_username',
|
||||
),
|
||||
]
|
||||
@@ -16,19 +16,6 @@ class AuthServicesInfo(models.Model):
|
||||
(MEMBER_STATE, 'Member'),
|
||||
)
|
||||
|
||||
ipboard_username = models.CharField(max_length=254, blank=True, default="")
|
||||
xenforo_username = models.CharField(max_length=254, blank=True, default="")
|
||||
forum_username = models.CharField(max_length=254, blank=True, default="")
|
||||
jabber_username = models.CharField(max_length=254, blank=True, default="")
|
||||
mumble_username = models.CharField(max_length=254, blank=True, default="")
|
||||
teamspeak3_uid = models.CharField(max_length=254, blank=True, default="")
|
||||
teamspeak3_perm_key = models.CharField(max_length=254, blank=True, default="")
|
||||
discord_uid = models.CharField(max_length=254, blank=True, default="")
|
||||
discourse_enabled = models.BooleanField(default=False, blank=True)
|
||||
ips4_username = models.CharField(max_length=254, blank=True, default="")
|
||||
ips4_id = models.CharField(max_length=254, blank=True, default="")
|
||||
smf_username = models.CharField(max_length=254, blank=True, default="")
|
||||
market_username = models.CharField(max_length=254, blank=True, default="")
|
||||
main_char_id = models.CharField(max_length=64, blank=True, default="")
|
||||
user = models.OneToOneField(User)
|
||||
state = models.CharField(blank=True, null=True, choices=STATE_CHOICES, default=NONE_STATE, max_length=10)
|
||||
|
||||
@@ -23,7 +23,7 @@ def pre_save_auth_state(sender, instance, *args, **kwargs):
|
||||
make_blue(instance)
|
||||
else:
|
||||
disable_member(instance.user)
|
||||
validate_services(instance.user, instance.state)
|
||||
validate_services.apply(args=(instance.user,))
|
||||
|
||||
|
||||
@receiver(post_save, sender=User)
|
||||
@@ -31,4 +31,4 @@ def post_save_user(sender, instance, created, *args, **kwargs):
|
||||
# ensure all users have a model
|
||||
if created:
|
||||
AuthServicesInfo.objects.get_or_create(user=instance)
|
||||
|
||||
|
||||
|
||||
@@ -20,14 +20,35 @@ def generate_alliance_group_name(alliancename):
|
||||
|
||||
|
||||
def disable_member(user):
|
||||
"""
|
||||
Disable a member who is transitioning to a NONE state.
|
||||
:param user: django.contrib.auth.models.User to disable
|
||||
:return:
|
||||
"""
|
||||
logger.debug("Disabling member %s" % user)
|
||||
if user.user_permissions.all().exists():
|
||||
logger.info("Clearning user %s permission to deactivate user." % user)
|
||||
user.user_permissions.clear()
|
||||
if user.groups.all().exists():
|
||||
logger.info("Clearing all non-public user %s groups to disable member." % user)
|
||||
user.groups.remove(*user.groups.filter(authgroup__public=False))
|
||||
validate_services.apply(args=(user,))
|
||||
|
||||
|
||||
def disable_user(user):
|
||||
"""
|
||||
Disable a user who is being set inactive or deleted
|
||||
:param user: django.contrib.auth.models.User to disable
|
||||
:return:
|
||||
"""
|
||||
logger.debug("Disabling user %s" % user)
|
||||
if user.user_permissions.all().exists():
|
||||
logger.info("Clearning user %s permission to deactivate user." % user)
|
||||
user.user_permissions.clear()
|
||||
if user.groups.all().exists():
|
||||
logger.info("Clearing user %s groups to deactivate user." % user)
|
||||
user.groups.clear()
|
||||
validate_services(user, None)
|
||||
validate_services.apply(args=(user,))
|
||||
|
||||
|
||||
def make_member(auth):
|
||||
@@ -110,6 +131,8 @@ def set_state(user):
|
||||
auth.state = state
|
||||
auth.save()
|
||||
notify(user, "Membership State Change", message="You membership state has been changed to %s" % state)
|
||||
assign_corp_group(auth)
|
||||
assign_alliance_group(auth)
|
||||
|
||||
|
||||
def assign_corp_group(auth):
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
# Create your tests here.
|
||||
0
authentication/tests/__init__.py
Normal file
84
authentication/tests/test_tasks.py
Normal file
@@ -0,0 +1,84 @@
|
||||
from __future__ import unicode_literals
|
||||
|
||||
try:
|
||||
# Py3
|
||||
from unittest import mock
|
||||
except ImportError:
|
||||
# Py2
|
||||
import mock
|
||||
|
||||
from django.test import TestCase
|
||||
from django.contrib.auth.models import Group, Permission
|
||||
|
||||
from alliance_auth.tests.auth_utils import AuthUtils
|
||||
|
||||
from authentication.tasks import disable_member, disable_user
|
||||
|
||||
|
||||
class AuthenticationTasksTestCase(TestCase):
|
||||
def setUp(self):
|
||||
self.member = AuthUtils.create_member('auth_member')
|
||||
self.none_user = AuthUtils.create_user('none_user', disconnect_signals=True)
|
||||
|
||||
@mock.patch('services.signals.transaction')
|
||||
def test_disable_member(self, transaction):
|
||||
# Inert signals action
|
||||
transaction.on_commit.side_effect = lambda fn: fn()
|
||||
|
||||
# Add permission
|
||||
perm = Permission.objects.create(codename='test_perm', name='test perm', content_type_id=1)
|
||||
|
||||
# Add public group
|
||||
pub_group = Group.objects.create(name="A Public group")
|
||||
pub_group.authgroup.internal = False
|
||||
pub_group.authgroup.public = True
|
||||
pub_group.save()
|
||||
|
||||
# Setup member
|
||||
self.member.user_permissions.add(perm)
|
||||
self.member.groups.add(pub_group)
|
||||
|
||||
# Pre assertion
|
||||
self.assertIn(pub_group, self.member.groups.all())
|
||||
self.assertGreater(len(self.member.groups.all()), 1)
|
||||
|
||||
# Act
|
||||
disable_member(self.member)
|
||||
|
||||
# Assert
|
||||
self.assertIn(pub_group, self.member.groups.all())
|
||||
# Everything but the single public group wiped
|
||||
self.assertEqual(len(self.member.groups.all()), 1)
|
||||
# All permissions wiped
|
||||
self.assertEqual(len(self.member.user_permissions.all()), 0)
|
||||
|
||||
@mock.patch('services.signals.transaction')
|
||||
def test_disable_user(self, transaction):
|
||||
# Inert signals action
|
||||
transaction.on_commit.side_effect = lambda fn: fn()
|
||||
|
||||
# Add permission
|
||||
perm = Permission.objects.create(codename='test_perm', name='test perm', content_type_id=1)
|
||||
|
||||
# Add public group
|
||||
pub_group = Group.objects.create(name="A Public group")
|
||||
pub_group.authgroup.internal = False
|
||||
pub_group.authgroup.public = True
|
||||
pub_group.save()
|
||||
|
||||
# Setup member
|
||||
self.member.user_permissions.add(perm)
|
||||
self.member.groups.add(pub_group)
|
||||
|
||||
# Pre assertion
|
||||
self.assertIn(pub_group, self.member.groups.all())
|
||||
self.assertGreater(len(self.member.groups.all()), 1)
|
||||
|
||||
# Act
|
||||
disable_user(self.member)
|
||||
|
||||
# Assert
|
||||
# All groups wiped
|
||||
self.assertEqual(len(self.member.groups.all()), 0)
|
||||
# All permissions wiped
|
||||
self.assertEqual(len(self.member.user_permissions.all()), 0)
|
||||
@@ -4,6 +4,7 @@ from django.contrib.auth import logout
|
||||
from django.contrib.auth import authenticate
|
||||
from django.shortcuts import render, redirect
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from eveonline.managers import EveManager
|
||||
from eveonline.models import EveCharacter
|
||||
from authentication.models import AuthServicesInfo
|
||||
@@ -34,10 +35,10 @@ def login_user(request):
|
||||
return redirect(redirect_to)
|
||||
else:
|
||||
logger.info("Login attempt failed for user %s: user marked inactive." % user)
|
||||
messages.warning(request, 'Your account has been disabled.')
|
||||
messages.warning(request, _('Your account has been disabled.'))
|
||||
else:
|
||||
logger.info("Failed login attempt: provided username %s" % form.cleaned_data['username'])
|
||||
messages.error(request, 'Username/password invalid.')
|
||||
messages.error(request, _('Username/password invalid.'))
|
||||
return render(request, 'public/login.html', context={'form': form})
|
||||
else:
|
||||
logger.debug("Providing new login form.")
|
||||
@@ -67,7 +68,8 @@ def register_user_view(request):
|
||||
|
||||
user.save()
|
||||
logger.info("Created new user %s" % user)
|
||||
messages.warning(request, 'Add an API key to set up your account.')
|
||||
login(request, user)
|
||||
messages.warning(request, _('Add an API key to set up your account.'))
|
||||
return redirect("auth_dashboard")
|
||||
|
||||
else:
|
||||
@@ -105,9 +107,10 @@ def sso_login(request, token):
|
||||
token.save()
|
||||
return redirect('auth_dashboard')
|
||||
else:
|
||||
messages.error(request, 'Your account has been disabled.')
|
||||
messages.error(request, _('Your account has been disabled.'))
|
||||
else:
|
||||
messages.warning(request, 'Authenticated character has no owning account. Please log in with username and password.')
|
||||
messages.warning(request,
|
||||
_('Authenticated character has no owning account. Please log in with username and password.'))
|
||||
except EveCharacter.DoesNotExist:
|
||||
messages.error(request, 'No account exists with the authenticated character. Please create an account first.')
|
||||
messages.error(request, _('No account exists with the authenticated character. Please create an account first.'))
|
||||
return redirect(login_user)
|
||||
|
||||
@@ -14,6 +14,7 @@ import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class CorpStats(models.Model):
|
||||
token = models.ForeignKey(Token, on_delete=models.CASCADE)
|
||||
@@ -40,36 +41,44 @@ class CorpStats(models.Model):
|
||||
|
||||
def update(self):
|
||||
try:
|
||||
c = self.token.get_esi_client()
|
||||
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(corporation_id=self.corp.corporation_id).result()
|
||||
c = self.token.get_esi_client(Character='v4', Corporation='v2')
|
||||
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(
|
||||
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
|
||||
# manual testing says we can do over 350, but let's not risk it
|
||||
member_id_chunks = [member_ids[i:i+255] for i in range(0, len(member_ids), 255)]
|
||||
member_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in member_id_chunks]
|
||||
member_id_chunks = [member_ids[i:i + 255] for i in range(0, len(member_ids), 255)]
|
||||
c = self.token.get_esi_client(Character='v1') # ccplease bump versions of whole resources
|
||||
member_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in
|
||||
member_id_chunks]
|
||||
member_list = {}
|
||||
for name_chunk in member_name_chunks:
|
||||
member_list.update({m['character_id']:m['character_name'] for m in name_chunk})
|
||||
member_list.update({m['character_id']: m['character_name'] for m in name_chunk})
|
||||
|
||||
self.members = member_list
|
||||
self.save()
|
||||
except TokenError as e:
|
||||
logger.warning("%s failed to update: %s" % (self, e))
|
||||
if self.token.user:
|
||||
notify(self.token.user, "%s failed to update with your ESI token." % self, message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.", level="error")
|
||||
notify(self.token.user, "%s failed to update with your ESI token." % self,
|
||||
message="Your token has expired or is no longer valid. Please add a new one to create a new CorpStats.",
|
||||
level="error")
|
||||
self.delete()
|
||||
except HTTPForbidden as e:
|
||||
logger.warning("%s failed to update: %s" % (self, e))
|
||||
if self.token.user:
|
||||
notify(self.token.user, "%s failed to update with your ESI token." % self, message="%s: %s" % (e.status_code, e.message), level="error")
|
||||
notify(self.token.user, "%s failed to update with your ESI token." % self,
|
||||
message="%s: %s" % (e.status_code, e.message), level="error")
|
||||
self.delete()
|
||||
except AssertionError:
|
||||
logger.warning("%s token character no longer in corp." % self)
|
||||
if self.token.user:
|
||||
notify(self.token.user, "%s cannot update with your ESI token." % self, message="%s cannot update with your ESI token as you have left corp." % self, level="error")
|
||||
notify(self.token.user, "%s cannot update with your ESI token." % self,
|
||||
message="%s cannot update with your ESI token as you have left corp." % self, level="error")
|
||||
self.delete()
|
||||
|
||||
@property
|
||||
@@ -95,7 +104,8 @@ class CorpStats(models.Model):
|
||||
char = EveCharacter.objects.get(character_id=auth.main_char_id)
|
||||
if char.corporation_id == self.corp.corporation_id and user.has_perm('corputils.corp_apis'):
|
||||
return True
|
||||
if self.corp.alliance and char.alliance_id == self.corp.alliance.alliance_id and user.has_perm('corputils.alliance_apis'):
|
||||
if self.corp.alliance and char.alliance_id == self.corp.alliance.alliance_id and user.has_perm(
|
||||
'corputils.alliance_apis'):
|
||||
return True
|
||||
if user.has_perm('corputils.blue_apis') and self.corp.is_blue:
|
||||
return True
|
||||
@@ -109,6 +119,12 @@ class CorpStats(models.Model):
|
||||
def member_count(self):
|
||||
return len(self.members)
|
||||
|
||||
def user_count(self, members):
|
||||
mainchars = []
|
||||
for member in members:
|
||||
if hasattr(member.main, 'character_name'):
|
||||
mainchars.append(member.main.character_name)
|
||||
return len(set(mainchars))
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class MemberObject(object):
|
||||
@@ -120,8 +136,10 @@ class CorpStats(models.Model):
|
||||
auth = AuthServicesInfo.objects.get(user=char.user)
|
||||
try:
|
||||
self.main = EveCharacter.objects.get(character_id=auth.main_char_id)
|
||||
self.main_user = self.main.character_name
|
||||
except EveCharacter.DoesNotExist:
|
||||
self.main = None
|
||||
self.main_user = ''
|
||||
api = EveApiKeyPair.objects.get(api_id=char.api_id)
|
||||
self.registered = True
|
||||
if show_apis:
|
||||
@@ -132,9 +150,11 @@ class CorpStats(models.Model):
|
||||
self.main = None
|
||||
self.api = None
|
||||
self.registered = False
|
||||
self.main_user = ''
|
||||
except EveApiKeyPair.DoesNotExist:
|
||||
self.api = None
|
||||
self.registered = False
|
||||
self.main_user = ''
|
||||
|
||||
def __str__(self):
|
||||
return self.character_name
|
||||
@@ -144,12 +164,14 @@ class CorpStats(models.Model):
|
||||
|
||||
def get_member_objects(self, user):
|
||||
show_apis = self.show_apis(user)
|
||||
return sorted([CorpStats.MemberObject(id, name, show_apis=show_apis) for id, name in self.members.items()], key=attrgetter('character_name'))
|
||||
member_list = [CorpStats.MemberObject(id, name, show_apis=show_apis) for id, name in self.members.items()]
|
||||
outlist = sorted([m for m in member_list if m.main_user], key=attrgetter('main_user', 'character_name'))
|
||||
outlist = outlist + sorted([m for m in member_list if not m.main_user], key=attrgetter('character_name'))
|
||||
return outlist
|
||||
|
||||
def can_update(self, user):
|
||||
return user.is_superuser or user == self.token.user
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ViewModel(object):
|
||||
def __init__(self, corpstats, user):
|
||||
@@ -157,6 +179,7 @@ class CorpStats(models.Model):
|
||||
self.members = corpstats.get_member_objects(user)
|
||||
self.can_update = corpstats.can_update(user)
|
||||
self.total_members = len(self.members)
|
||||
self.total_users = corpstats.user_count(self.members)
|
||||
self.registered_members = corpstats.entered_apis()
|
||||
self.show_apis = corpstats.show_apis(user)
|
||||
self.last_updated = corpstats.last_update
|
||||
|
||||
14
corputils/tasks.py
Normal file
@@ -0,0 +1,14 @@
|
||||
from corputils.models import CorpStats
|
||||
from alliance_auth.celeryapp import app
|
||||
|
||||
|
||||
@app.task
|
||||
def update_corpstats(pk):
|
||||
cs = CorpStats.objects.get(pk=pk)
|
||||
cs.update()
|
||||
|
||||
|
||||
@app.task
|
||||
def update_all_corpstats():
|
||||
for cs in CorpStats.objects.all():
|
||||
update_corpstats.delay(cs.pk)
|
||||
@@ -10,7 +10,7 @@
|
||||
<div class="container-fluid">
|
||||
<ul class="nav navbar-nav">
|
||||
<li class="dropdown">
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">Corporations<span class="caret"></span></a>
|
||||
<a href="#" class="dropdown-toggle" data-toggle="dropdown" role="button" aria-haspopup="true" aria-expanded="false">{% trans "Corporations" %}<span class="caret"></span></a>
|
||||
<ul class="dropdown-menu">
|
||||
{% for corpstat in available %}
|
||||
<li>
|
||||
@@ -21,13 +21,13 @@
|
||||
</li>
|
||||
{% if perms.corputils.add_corpstats %}
|
||||
<li>
|
||||
<a href="{% url 'corputils:add' %}">Add</a>
|
||||
<a href="{% url 'corputils:add' %}">{% trans "Add" %}</a>
|
||||
</li>
|
||||
{% endif %}
|
||||
</ul>
|
||||
<form class="navbar-form navbar-right" role="search" action="{% url 'corputils:search' %}" method="GET">
|
||||
<div class="form-group">
|
||||
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}Search characters...{% endif %}">
|
||||
<input type="text" class="form-control" name="search_string" placeholder="{% if search_string %}{{ search_string }}{% else %}{% trans "Search characters..." %}{% endif %}">
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
|
||||
@@ -2,6 +2,7 @@
|
||||
{% load i18n %}
|
||||
{% load humanize %}
|
||||
{% load bootstrap_pagination %}
|
||||
{% load eveonline_extras %}
|
||||
{% block member_data %}
|
||||
{% if corpstats %}
|
||||
<div class="row">
|
||||
@@ -24,7 +25,7 @@
|
||||
</div>
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
<b>{% trans "API Index:" %}</b>
|
||||
<b>{% trans "API Index: " %}</b> {{ corpstats.total_users }} Main Character{{ corpstats.total_users|pluralize }}
|
||||
<div class="progress">
|
||||
<div class="progress-bar" role="progressbar" aria-valuenow="{{ corpstats.registered_members }}" aria-valuemin="0" aria-valuemax="{{ corpstats.total_members }}" style="width: {% widthratio corpstats.registered_members corpstats.total_members 100 %}%;">
|
||||
{{ corpstats.registered_members }}/{{ corpstats.total_members }}
|
||||
@@ -37,10 +38,10 @@
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading clearfix">
|
||||
<div class="panel-title pull-left">
|
||||
<h4>Members</h4>
|
||||
<h4>{% trans "Members" %}</h4>
|
||||
</div>
|
||||
<div class="panel-title pull-right">
|
||||
Last update: {{ corpstats.last_updated|naturaltime }}
|
||||
{% trans "Last update:" %} {{ corpstats.last_updated|naturaltime }}
|
||||
{% if corpstats.can_update %}
|
||||
<a class="btn btn-success" type="button" href="{% url 'corputils:update' corpstats.corp.corporation_id %}" title="Update Now">
|
||||
<span class="glyphicon glyphicon-refresh"></span>
|
||||
@@ -56,14 +57,14 @@
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th></th>
|
||||
<th class="text-center">Character</th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
{% if corpstats.show_apis %}
|
||||
<th class="text-center">API</th>
|
||||
{% endif %}
|
||||
<th class="text-center">zKillboard</th>
|
||||
<th class="text-center">Main Character</th>
|
||||
<th class="text-center">Main Corporation</th>
|
||||
<th class="text-center">Main Alliance</th>
|
||||
<th class="text-center">{% trans "zKillboard" %}</th>
|
||||
<th class="text-center">{% trans "Main Character" %}</th>
|
||||
<th class="text-center">{% trans "Main Corporation" %}</th>
|
||||
<th class="text-center">{% trans "Main Alliance" %}</th>
|
||||
</tr>
|
||||
{% for member in members %}
|
||||
<tr {% if not member.registered %}class="danger"{% endif %}>
|
||||
@@ -71,7 +72,7 @@
|
||||
<td class="text-center">{{ member.character_name }}</td>
|
||||
{% if corpstats.show_apis %}
|
||||
{% if member.api %}
|
||||
<td class="text-center"><a href="{{ JACK_KNIFE_URL }}?usid={{ member.api.api_id }}&apik={{ member.api.api_key }}" target="_blank" class="label label-primary">{{ member.api.api_id }}</td>
|
||||
<td class="text-center">{{ member.api|api_link:'label label-primary' }}</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
{% extends "corputils/base.html" %}
|
||||
{% load i18n %}
|
||||
{% load bootstrap_pagination %}
|
||||
{% load eveonline_extras %}
|
||||
{% block member_data %}
|
||||
<div class="panel panel-default">
|
||||
<div class="panel-heading clearfix">
|
||||
<div class="panel-title pull-left">Search Results</div>
|
||||
<div class="panel-title pull-left">{% trans "Search Results" %}</div>
|
||||
</div>
|
||||
<div class="panel-body">
|
||||
<div class="text-center">
|
||||
@@ -13,13 +14,13 @@
|
||||
<table class="table table-hover">
|
||||
<tr>
|
||||
<th class="text-center"></th>
|
||||
<th class="text-center">Character</th>
|
||||
<th class="text-center">Corporation</th>
|
||||
<th class="text-center">API</th>
|
||||
<th class="text-center">zKillboard</th>
|
||||
<th class="text-center">Main Character</th>
|
||||
<th class="text-center">Main Corporation</th>
|
||||
<th class="text-center">Main Alliance</th>
|
||||
<th class="text-center">{% trans "Character" %}</th>
|
||||
<th class="text-center">{% trans "Corporation" %}</th>
|
||||
<th class="text-center">{% trans "API" %}</th>
|
||||
<th class="text-center">{% trans "zKillboard" %}</th>
|
||||
<th class="text-center">{% trans "Main Character" %}</th>
|
||||
<th class="text-center">{% trans "Main Corporation" %}</th>
|
||||
<th class="text-center">{% trans "Main Alliance" %}</th>
|
||||
</tr>
|
||||
{% for result in results %}
|
||||
<tr {% if not result.1.registered %}class="danger"{% endif %}>
|
||||
@@ -27,7 +28,7 @@
|
||||
<td class="text-center">{{ result.1.character_name }}</td>
|
||||
<td class="text-center">{{ result.0.corp.corporation_name }}</td>
|
||||
{% if result.1.api %}
|
||||
<td class="text-center"><a href="{{ JACK_KNIFE_URL }}?usid={{ result.1.api.api_id }}&apik={{ result.1.api.api_key }}" target="_blank" class="label label-primary">{{ result.1.api.api_id }}</td>
|
||||
<td class="text-center">{{ result.1.api|api_link:"label label-primary" }}</td>
|
||||
{% else %}
|
||||
<td></td>
|
||||
{% endif %}
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
from django.conf.urls import url
|
||||
import corputils.views
|
||||
|
||||
app_name='corputils'
|
||||
app_name = 'corputils'
|
||||
urlpatterns = [
|
||||
url(r'^$', corputils.views.corpstats_view, name='view'),
|
||||
url(r'^add/$', corputils.views.corpstats_add, name='add'),
|
||||
|
||||
@@ -6,13 +6,16 @@ from django.contrib import messages
|
||||
from django.core.exceptions import PermissionDenied
|
||||
from django.db import IntegrityError
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.conf import settings
|
||||
from eveonline.models import EveCharacter, EveCorporationInfo
|
||||
from corputils.models import CorpStats
|
||||
from esi.decorators import token_required
|
||||
from bravado.exception import HTTPError
|
||||
|
||||
MEMBERS_PER_PAGE = int(getattr(settings, 'CORPSTATS_MEMBERS_PER_PAGE', 20))
|
||||
|
||||
|
||||
def get_page(model_list, page_num):
|
||||
p = Paginator(model_list, MEMBERS_PER_PAGE)
|
||||
try:
|
||||
@@ -23,8 +26,11 @@ def get_page(model_list, page_num):
|
||||
members = p.page(p.num_pages)
|
||||
return members
|
||||
|
||||
|
||||
def access_corpstats_test(user):
|
||||
return user.has_perm('corputils.view_corp_corpstats') or user.has_perm('corputils.view_alliance_corpstats') or user.has_perm('corputils.view_blue_corpstats')
|
||||
return user.has_perm('corputils.view_corp_corpstats') or user.has_perm(
|
||||
'corputils.view_alliance_corpstats') or user.has_perm('corputils.view_blue_corpstats')
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(access_corpstats_test)
|
||||
@@ -35,26 +41,31 @@ def corpstats_add(request, token):
|
||||
if EveCharacter.objects.filter(character_id=token.character_id).exists():
|
||||
corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
|
||||
else:
|
||||
corp_id = token.get_esi_client().Character.get_characters_character_id(character_id=token.character_id).result()['corporation_id']
|
||||
corp_id = \
|
||||
token.get_esi_client(Character='v4').Character.get_characters_character_id(character_id=token.character_id).result()[
|
||||
'corporation_id']
|
||||
corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
|
||||
cs = CorpStats.objects.create(token=token, corp=corp)
|
||||
cs.update()
|
||||
assert cs.pk # ensure update was succesful
|
||||
try:
|
||||
cs.update()
|
||||
except HTTPError as e:
|
||||
messages.error(request, str(e))
|
||||
assert cs.pk # ensure update was successful
|
||||
if CorpStats.objects.filter(pk=cs.pk).visible_to(request.user).exists():
|
||||
return redirect('corputils:view_corp', corp_id=corp.corporation_id)
|
||||
except EveCorporationInfo.DoesNotExist:
|
||||
messages.error(request, 'Unrecognized corporation. Please ensure it is a member of the alliance or a blue.')
|
||||
messages.error(request, _('Unrecognized corporation. Please ensure it is a member of the alliance or a blue.'))
|
||||
except IntegrityError:
|
||||
messages.error(request, 'Selected corp already has a statistics module.')
|
||||
messages.error(request, _('Selected corp already has a statistics module.'))
|
||||
except AssertionError:
|
||||
messages.error(request, 'Failed to gather corporation statistics with selected token.')
|
||||
messages.error(request, _('Failed to gather corporation statistics with selected token.'))
|
||||
return redirect('corputils:view')
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(access_corpstats_test)
|
||||
def corpstats_view(request, corp_id=None):
|
||||
corpstats = None
|
||||
show_apis = False
|
||||
|
||||
# get requested model
|
||||
if corp_id:
|
||||
@@ -65,7 +76,7 @@ def corpstats_view(request, corp_id=None):
|
||||
available = CorpStats.objects.visible_to(request.user)
|
||||
|
||||
# ensure we can see the requested model
|
||||
if corpstats and not corpstats in available:
|
||||
if corpstats and corpstats not in available:
|
||||
raise PermissionDenied('You do not have permission to view the selected corporation statistics module.')
|
||||
|
||||
# get default model if none requested
|
||||
@@ -84,22 +95,31 @@ def corpstats_view(request, corp_id=None):
|
||||
|
||||
if corpstats:
|
||||
context.update({
|
||||
'corpstats': corpstats.get_view_model(request.user),
|
||||
'members': members,
|
||||
'corpstats': corpstats.get_view_model(request.user),
|
||||
'members': members,
|
||||
})
|
||||
|
||||
return render(request, 'corputils/corpstats.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(access_corpstats_test)
|
||||
def corpstats_update(request, corp_id):
|
||||
corp = get_object_or_404(EveCorporationInfo, corporation_id=corp_id)
|
||||
corpstats = get_object_or_404(CorpStats, corp=corp)
|
||||
if corpstats.can_update(request.user):
|
||||
corpstats.update()
|
||||
try:
|
||||
corpstats.update()
|
||||
except HTTPError as e:
|
||||
messages.error(request, str(e))
|
||||
else:
|
||||
raise PermissionDenied('You do not have permission to update member data for the selected corporation statistics module.')
|
||||
return redirect('corputils:view_corp', corp_id=corp.corporation_id)
|
||||
raise PermissionDenied(
|
||||
'You do not have permission to update member data for the selected corporation statistics module.')
|
||||
if corpstats.pk:
|
||||
return redirect('corputils:view_corp', corp_id=corp.corporation_id)
|
||||
else:
|
||||
return redirect('corputils:view')
|
||||
|
||||
|
||||
@login_required
|
||||
@user_passes_test(access_corpstats_test)
|
||||
@@ -109,9 +129,11 @@ def corpstats_search(request):
|
||||
if search_string:
|
||||
has_similar = CorpStats.objects.filter(_members__icontains=search_string).visible_to(request.user)
|
||||
for corpstats in has_similar:
|
||||
similar = [(member_id, corpstats.members[member_id]) for member_id in corpstats.members if search_string.lower() in corpstats.members[member_id].lower()]
|
||||
similar = [(member_id, corpstats.members[member_id]) for member_id in corpstats.members if
|
||||
search_string.lower() in corpstats.members[member_id].lower()]
|
||||
for s in similar:
|
||||
results.append((corpstats, CorpStats.MemberObject(s[0], s[1], show_apis=corpstats.show_apis(request.user))))
|
||||
results.append(
|
||||
(corpstats, CorpStats.MemberObject(s[0], s[1], show_apis=corpstats.show_apis(request.user))))
|
||||
page = request.GET.get('page', 1)
|
||||
results = sorted(results, key=lambda x: x[1].character_name)
|
||||
results_page = get_page(results, page)
|
||||
|
||||
BIN
docs/_static/images/features/corpstats/api_index.png
vendored
Normal file
|
After Width: | Height: | Size: 2.7 KiB |
BIN
docs/_static/images/features/corpstats/blank_header.png
vendored
Normal file
|
After Width: | Height: | Size: 14 KiB |
BIN
docs/_static/images/features/corpstats/eve_sso_authorization.png
vendored
Normal file
|
After Width: | Height: | Size: 258 KiB |
BIN
docs/_static/images/features/corpstats/last_update.png
vendored
Normal file
|
After Width: | Height: | Size: 4.2 KiB |
BIN
docs/_static/images/features/corpstats/member_list.png
vendored
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
docs/_static/images/features/corpstats/navbar.png
vendored
Normal file
|
After Width: | Height: | Size: 4.9 KiB |
BIN
docs/_static/images/features/corpstats/pagination.png
vendored
Normal file
|
After Width: | Height: | Size: 903 B |
BIN
docs/_static/images/features/corpstats/search_view.png
vendored
Normal file
|
After Width: | Height: | Size: 30 KiB |
BIN
docs/_static/images/features/corpstats/select_sso_token.png
vendored
Normal file
|
After Width: | Height: | Size: 24 KiB |
BIN
docs/_static/images/features/group-admin.png
vendored
Normal file
|
After Width: | Height: | Size: 54 KiB |
BIN
docs/_static/images/features/group-member-management.png
vendored
Normal file
|
After Width: | Height: | Size: 20 KiB |
BIN
docs/_static/images/features/group-membership.png
vendored
Normal file
|
After Width: | Height: | Size: 36 KiB |
@@ -4,4 +4,6 @@
|
||||
.. toctree::
|
||||
|
||||
documentation
|
||||
integrating-services
|
||||
menu-hooks
|
||||
```
|
||||
|
||||
302
docs/development/integrating-services.md
Normal file
@@ -0,0 +1,302 @@
|
||||
# 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.
|
||||
|
||||
## The Service Module
|
||||
|
||||
Each service module is its own self contained Django app. It will likely contain views, models, migrations and templates. Anything that is valid in a Django app is valid in a service module.
|
||||
|
||||
Normally service modules live in `services.modules` though they may also be installed as external packages and installed via `pip` if you wish. A module is installed by including it in the `INSTALLED_APPS` setting.
|
||||
|
||||
### Service Module Structure
|
||||
|
||||
Typically a service will contain 5 key components:
|
||||
|
||||
- [The Hook](#the-hook)
|
||||
- [The Service Manager](#the-service-manager)
|
||||
- [The Views](#the-views)
|
||||
- [The Tasks](#the-tasks)
|
||||
- [The Models](#the-models)
|
||||
|
||||
The architecture looks something like this:
|
||||
|
||||
urls -------▶ Views
|
||||
▲ |
|
||||
| |
|
||||
| ▼
|
||||
ServiceHook ----▶ Tasks ----▶ Manager
|
||||
▲
|
||||
|
|
||||
|
|
||||
AllianceAuth
|
||||
|
||||
|
||||
Where:
|
||||
Module --▶ Dependency/Import
|
||||
|
||||
While this is the typical structure of the existing services modules, there is no enforcement of this structure and you are, effectively, free to create whatever architecture may be necessary. A service module need not even communicate with an external service, for example, if similar triggers such as validate_user, delete_user are required for a module it may be convenient to masquerade as a service. Ideally though, using the common structure improves the maintainability for other developers.
|
||||
|
||||
### The Hook
|
||||
|
||||
In order to integrate with Alliance Auth service modules must provide a `services_hook`. This hook will be a function that returns an instance of the `services.hooks.ServiceHook` class and decorated with the `@hooks.registerhook` decorator. For example:
|
||||
|
||||
@hooks.register('services_hook')
|
||||
def register_service():
|
||||
return ExampleService()
|
||||
|
||||
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.
|
||||
```
|
||||
|
||||
|
||||
A subclassed `ServiceHook` might look like this:
|
||||
|
||||
class ExampleService(ServicesHook):
|
||||
def __init__(self):
|
||||
ServicesHook.__init__(self)
|
||||
self.urlpatterns = urlpatterns
|
||||
self.service_url = 'http://exampleservice.example.com'
|
||||
|
||||
"""
|
||||
Overload base methods here to implement functionality
|
||||
"""
|
||||
|
||||
|
||||
### The ServiceHook class
|
||||
|
||||
The base `ServiceHook` class defines function signatures that Alliance Auth will call under certain conditions in order to trigger some action in the service.
|
||||
|
||||
You will need to subclass `services.hooks.ServiceHook` in order to provide implementation of the functions so that Alliance Auth can interact with the service correctly. All of the functions are optional, so its up to you to define what you need.
|
||||
|
||||
Instance Variables:
|
||||
- [self.name](#self-name)
|
||||
- [self.urlpatterns](#self-url-patterns)
|
||||
- [self.service_ctrl_template](#self-service-ctrl-template)
|
||||
|
||||
Properties:
|
||||
- [title](#title)
|
||||
|
||||
Functions:
|
||||
- [delete_user](#delete-user)
|
||||
- [validate_user](#validate-user)
|
||||
- [sync_nickname](#sync-nickname)
|
||||
- [update_groups](#update-groups)
|
||||
- [update_all_groups](#update-all-groups)
|
||||
- [service_enabled_members](#service-enabled-members)
|
||||
- [service_enabled_blues](#service-enabled-blues)
|
||||
- [service_active_for_user](#service-active-for-user)
|
||||
- [show_service_ctrl](#show-service-ctrl)
|
||||
- [render_service_ctrl](#render-service-ctrl)
|
||||
|
||||
|
||||
#### self.name
|
||||
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.
|
||||
|
||||
from . import urls
|
||||
...
|
||||
class MyService(ServiceHook):
|
||||
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
|
||||
This is provided as a courtesy and defines the default template to be used with [render_service_ctrl](#render-service-ctrl). You are free to redefine or not use this variable at all.
|
||||
|
||||
#### title
|
||||
This is a property which provides a user friendly display of your service's name. It will usually do a reasonably good job unless your service name has punctuation or odd capitalisation. If this is the case you should override this method and return a string.
|
||||
|
||||
#### delete_user
|
||||
`def delete_user(self, user, notify_user=False):`
|
||||
|
||||
Delete the users service account, optionally notify them that the service has been disabled. The `user` parameter should be a Django User object. If notify_user is set to `True` a message should be set to the user via the `notifications` module to alert them that their service account has been disabled.
|
||||
|
||||
The function should return a boolean, `True` if successfully disabled, `False` otherwise.
|
||||
|
||||
#### validate_user
|
||||
`def validate_user(self, user):`
|
||||
|
||||
Validate the users service account, deleting it if they should no longer have access. The `user` parameter should be a Django User object.
|
||||
|
||||
An implementation will probably look like the following:
|
||||
|
||||
def validate_user(self, user):
|
||||
logger.debug('Validating user %s %s account' % (user, self.name))
|
||||
if ExampleTasks.has_account(user) and not self.service_active_for_user(user):
|
||||
self.delete_user(user, notify_user=True)
|
||||
|
||||
No return value is expected.
|
||||
|
||||
This function will be called periodically on all users to validate that the given user should have their current service accounts.
|
||||
|
||||
#### sync_nickname
|
||||
`def sync_nickname(self, user):`
|
||||
|
||||
Very optional. As of writing only one service defines this. The `user` parameter should be a Django User object. When called, the given users nickname for the service should be updated and synchronised with the service.
|
||||
|
||||
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.
|
||||
|
||||
#### update_groups
|
||||
`def update_groups(self, user):`
|
||||
|
||||
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.
|
||||
|
||||
This action is usually called via a signal when a users group membership changes (joins or leaves a group).
|
||||
|
||||
#### update_all_groups
|
||||
`def update_all_groups(self):`
|
||||
|
||||
The service should iterate through all of its recorded users and update their groups.
|
||||
|
||||
I'm really not sure when this is called, it may have been a hold over from before signals started to be used. Regardless, it can be useful to server admins who may call this from a Django shell to force a synchronisation of all user groups for a specific service.
|
||||
|
||||
#### service_enabled_members
|
||||
`def service_enabled_members(self):`
|
||||
|
||||
Is this service enabled for users with the `Member` state? It should return `True` if the service is enabled for Members, and `False` otherwise. The default is `False`.
|
||||
|
||||
An implementation will usually look like:
|
||||
|
||||
def service_enabled_members(self):
|
||||
return settings.ENABLE_AUTH_EXAMPLE or False
|
||||
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
There has been discussion about moving services to permissions based access. You should review `Issue #663 <https://github.com/allianceauth/allianceauth/issues/663/>`_.
|
||||
```
|
||||
|
||||
#### service_enabled_blues
|
||||
`def service_enabled_blues(self):`
|
||||
|
||||
Is this service enabled for users with the `Blue` state? It should return `True` if the service is enabled for Blues, and `False` otherwise. The default is `False`.
|
||||
|
||||
An implementation will usually look like:
|
||||
|
||||
def service_enabled_blues(self):
|
||||
return settings.ENABLE_BLUE_EXAMPLE or False
|
||||
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
There has been discussion about moving services to permissions based access. You should review `Issue #663 <https://github.com/allianceauth/allianceauth/issues/663/>`_.
|
||||
```
|
||||
|
||||
#### service_active_for_user
|
||||
`def service_active_for_user(self, user):`
|
||||
|
||||
Is this service active for the given user? The `user` parameter should be a Django User object.
|
||||
|
||||
Usually you wont need to override this as it calls `service_enabled_members` or `service_enabled_blues` depending on the users state.
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
There has been discussion about moving services to permissions based access. You should review `Issue #663 <https://github.com/allianceauth/allianceauth/issues/663/>`_ as this function will likely need to be defined by each service to check its permission.
|
||||
```
|
||||
|
||||
#### show_service_ctrl
|
||||
`def show_service_ctrl(self, user, state):`
|
||||
|
||||
Should the service be shown for the given `user` with the given `state`? The `user` parameter should be a Django User object, and the `state` parameter should be a valid state from `authentication.states`.
|
||||
|
||||
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):`
|
||||
|
||||
Render the services control row. This will be called for all active services when a user visits the `/services/` page and [show_service_ctrl](#show-service-ctrl) returns `True` for the given user.
|
||||
|
||||
It should return a string (usually from `render_to_string`) of a table row (`<tr>`) with 4 columns (`<td>`). Column #1 is the service name, column #2 is the users username for this service, column #3 is the services URL, and column #4 is the action buttons.
|
||||
|
||||
You may either define your own service template or use the default one provided. The default can be used like this example:
|
||||
|
||||
def render_services_ctrl(self, request):
|
||||
"""
|
||||
Example for rendering the service control panel row
|
||||
You can override the default template and create a
|
||||
custom one if you wish.
|
||||
:param request:
|
||||
:return:
|
||||
"""
|
||||
urls = self.Urls()
|
||||
urls.auth_activate = 'auth_example_activate'
|
||||
urls.auth_deactivate = 'auth_example_deactivate'
|
||||
urls.auth_reset_password = 'auth_example_reset_password'
|
||||
urls.auth_set_password = 'auth_example_set_password'
|
||||
return render_to_string(self.service_ctrl_template, {
|
||||
'service_name': self.title,
|
||||
'urls': urls,
|
||||
'service_url': self.service_url,
|
||||
'username': 'example username'
|
||||
}, request=request)
|
||||
|
||||
the `Urls` class defines the available URL names for the 4 actions available in the default template:
|
||||
|
||||
- Activate (create service account)
|
||||
- Deactivate (delete service account)
|
||||
- Reset Password (random password)
|
||||
- Set Password (custom password)
|
||||
|
||||
If you don't define one or all of these variable the button for the undefined URLs will not be displayed.
|
||||
|
||||
Most services will survive with the default template. If, however, you require extra buttons for whatever reason, you are free to provide your own template as long as you stick within the 4 columns. Multiple rows should be OK, though may be confusing to users.
|
||||
|
||||
### Menu Item Hook
|
||||
|
||||
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.
|
||||
|
||||
### The Service Manager
|
||||
|
||||
The service manager is what interacts with the external service. Ideally it should be completely agnostic about its environment, meaning that it should avoid calls to Alliance Auth and Django in general (except in special circumstances where the service is managed locally, e.g. Mumble). Data should come in already arranged by the Tasks and data passed back for the tasks to manage or distribute.
|
||||
|
||||
The reason for maintaining this separation is that managers may be reused from other sources and there may not even be a need to write a custom manager. Likewise, by maintaining this neutral environment others may reuse the managers that we write. It can also significantly ease the unit testing of services.
|
||||
|
||||
### The Views
|
||||
|
||||
As mentioned at the start of this page, service modules are fully fledged Django apps. This means you're free to do whatever you wish with your views.
|
||||
|
||||
Typically most traditional username/password services define four views.
|
||||
|
||||
- Create Account
|
||||
- Delete Account
|
||||
- Reset Password
|
||||
- Set Password
|
||||
|
||||
These views should interact with the service via the Tasks, though in some instances may bypass the Tasks and access the manager directly where necessary, for example OAuth functionality.
|
||||
|
||||
|
||||
### The Tasks
|
||||
|
||||
The tasks component is the glue that holds all of the other components of the service module together. It provides the function implementation to handle things like adding and deleting users, updating groups, validating the existence of a users account. Whatever tasks `auth_hooks` and `views` have with interacting with the service will probably live here.
|
||||
|
||||
### The Models
|
||||
|
||||
Its very likely that you'll need to store data about a users remote service account locally. As service modules are fully fledged Django apps you are free to create as many models as necessary for persistent storage. You can create foreign keys to other models in Alliance Auth if necessary, though I _strongly_ recommend you limit this to the User and Groups models from `django.contrib.auth.models` and query any other data manually.
|
||||
|
||||
If you create models you should create the migrations that go along with these inside of your module/app.
|
||||
|
||||
|
||||
## Examples
|
||||
|
||||
There is a bare bones example service included in `services.modules.example`, you may like to use this as the base for your new service.
|
||||
|
||||
You should have a look through some of the other service modules before you get started to get an idea of the general structure of one. A lot of them aren't perfect so don't feel like you have to rigidly follow the structure of the existing services if you think its sub-optimal or doesn't suit the external service you're integrating.
|
||||
|
||||
|
||||
## Testing
|
||||
You will need to add unit tests for all aspects of your service module before it is accepted. Be mindful that you don't actually want to make external calls to the service so you should mock the appropriate components to prevent this behaviour.
|
||||
38
docs/development/menu-hooks.md
Normal file
@@ -0,0 +1,38 @@
|
||||
# Menu Hooks
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
Currently most menu items are statically defined in the `base.html` template. Ideally this behaviour will change over time with each module of Alliance Auth providing all of its menu items via the hook. New modules should aim to use the hook over statically adding menu items to the base template.
|
||||
```
|
||||
|
||||
The menu hooks allow you to dynamically specify menu items from your plugin app or service. To achieve this you should subclass or instantiate the `services.hooks.MenuItemHook` class and then register the menu item with one of the hooks.
|
||||
|
||||
There are three levels of Menu Item Hooks
|
||||
|
||||
- `menu_main_hook`
|
||||
- `menu_aux_hook`
|
||||
- `menu_util_hook`
|
||||
|
||||
These represent the 3 levels of menu displayed on the site.
|
||||
|
||||
To register a MenuItemHook class you would do the following:
|
||||
|
||||
@hooks.register('menu_util_hook')
|
||||
def register_menu():
|
||||
return MenuItemHook('Example Item', 'glyphicon glyphicon-heart', 'example_url_name', 150)
|
||||
|
||||
|
||||
The `MenuItemHook` class specifies some parameters/instance variables required for menu item display.
|
||||
|
||||
`MenuItemHook(text, classes, url_name, order=None)`
|
||||
|
||||
#### text
|
||||
The text value of the link
|
||||
#### classes
|
||||
The classes that should be applied to the bootstrap menu item icon
|
||||
#### url_name
|
||||
The name of the Django URL to use
|
||||
#### order
|
||||
An integer which specifies the order of the menu item, lowest to highest
|
||||
|
||||
If you cannot get the menu item to look the way you wish, you are free to subclass and override the default render function and the template used.
|
||||
@@ -6,17 +6,17 @@ This module is used to check the registration status of corp members and to dete
|
||||
|
||||
Upon initial install, nothing will be visible. For every corp, a model will have to be created before data can be viewed.
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||

|
||||

|
||||
|
||||
You will return to auth where you are asked to select a token with the green arrow button. If you want to use a different character, press the `LOG IN with EVE Online` button.
|
||||
|
||||

|
||||

|
||||
|
||||
If this works (and you have permission to view the Corp Stats you just created) you'll be returned to a view of the Corp Stats.
|
||||
If it fails an error message will be displayed.
|
||||
@@ -25,7 +25,7 @@ If it fails an error message will be displayed.
|
||||
|
||||
### Navigation Bar
|
||||
|
||||

|
||||

|
||||
|
||||
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.
|
||||
|
||||
@@ -33,13 +33,13 @@ On the right of this bar is a search field. Press enter to search. It checks all
|
||||
|
||||
### API Index
|
||||
|
||||

|
||||

|
||||
|
||||
This is a visual indication of the number of registered characters.
|
||||
|
||||
### Last Update
|
||||
|
||||

|
||||

|
||||
|
||||
Corp Stats do not automatically update. They update once upon creation for initial data, and whenever someone presses the update button.
|
||||
|
||||
@@ -47,7 +47,7 @@ Only superusers and the creator of the Corp Stat can update it.
|
||||
|
||||
### Member List
|
||||
|
||||

|
||||

|
||||
|
||||
The list contains all characters in the corp. Red backgrounds means they are not registered in auth. If registered, and the user has the required permission to view APIs, a link to JackKnife will be present.
|
||||
A link to zKillboard is present for all characters.
|
||||
@@ -55,11 +55,11 @@ If registered, the character will also have a main character, main corporation,
|
||||
|
||||
This view is paginated: use the navigation arrows to view more pages (sorted alphabetically by character name), or search for a specific character.
|
||||
|
||||

|
||||

|
||||
|
||||
## Search View
|
||||
|
||||

|
||||

|
||||
|
||||
This view is essentially the same as the Corp Stats page, but not specific to a single corp.
|
||||
The search query is visible in the search box.
|
||||
|
||||
101
docs/features/groups.md
Normal file
@@ -0,0 +1,101 @@
|
||||
# Groups
|
||||
Group Management is one of the core tasks of Alliance Auth. Many of Alliance Auth's services allow for synchronising of group membership, allowing you to grant permissions or roles in services to access certain aspects of them.
|
||||
|
||||
## Automatic Groups
|
||||
When a member registers in Alliance Auth and selects a main character, Auth will assign them some groups automatically based on some factors.
|
||||
|
||||
```eval_rst
|
||||
.. important::
|
||||
The ``Corp_`` and ``Alliance_`` group name prefixes are reserved for Alliance Auth internal group management. If you prefix a group with these you will find Alliance Auth automatically removes users from the group.
|
||||
```
|
||||
|
||||
```eval_rst
|
||||
+------------------------------+-----------------------------------------------------------------------------------+
|
||||
| Group | Condition |
|
||||
+------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``Corp_<corp_name>`` | Users Main Character belongs to the Corporation |
|
||||
+------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``Alliance_<alliance_name>`` | Users Main Character belongs to the Alliance |
|
||||
+------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``Member`` | User is a member of one of the tenant Corps or Alliances |
|
||||
+------------------------------+-----------------------------------------------------------------------------------+
|
||||
| ``Blue`` | User is a member of a blue Corp or Alliance, be it via standings or static config |
|
||||
+------------------------------+-----------------------------------------------------------------------------------+
|
||||
```
|
||||
|
||||
When the user no longer has the condition required to be a member of that group they are automatically removed by Auth.
|
||||
|
||||
## User Organised Groups
|
||||
|
||||
Along with the automated groups, administrators can create custom groups for users to join. Examples might be groups like `Leadership`, `CEO` or `Scouts`.
|
||||
|
||||
When you create a Django `Group`, Auth automatically creates a corresponding `AuthGroup` model. The admin page looks like this:
|
||||
|
||||

|
||||
|
||||
Here you have several options:
|
||||
|
||||
#### Internal
|
||||
Users cannot see, join or request to join this group. This is primarily used for Auth's internally managed groups, though can be useful if you want to prevent users from managing their membership of this group themselves. This option will override the Hidden, Open and Public options when enabled.
|
||||
|
||||
By default, every new group created will be an internal group.
|
||||
|
||||
#### Hidden
|
||||
Group is hidden from the user interface, but users can still join if you give them the appropriate join link. The URL will be along the lines of `https://example.com/en/group/request_add/{group_id}`. You can get the Group ID from the admin page URL.
|
||||
|
||||
This option still respects the Open option.
|
||||
|
||||
|
||||
### Open
|
||||
When a group is toggled open, users who request to join the group will be immediately added to the group.
|
||||
|
||||
If the group is not open, their request will have to be approved manually by someone with the group management role, or a group leader of that group.
|
||||
|
||||
|
||||
### Public
|
||||
Group is accessible to any registered user, even when they do not have permission to join regular groups.
|
||||
|
||||
The key difference is that the group is completely unmanaged by Auth. **Once a member joins they will not be removed unless they leave manually, you remove them manually, or their account is deliberately set inactive or deleted.**
|
||||
|
||||
Most people won't have a use for public groups, though it can be useful if you wish to allow public access to some services. You can grant service permissions on a public group to allow this behaviour.
|
||||
|
||||
|
||||
## Permission
|
||||
In order to join a group other than a public group, the permission `groupmanagement.request_groups` (`Can request non-public groups` in the admin panel) must be active on their account, either via a group or directly applied to their User account.
|
||||
|
||||
When a user loses this permission, they will be removed from all groups _except_ Public groups.
|
||||
|
||||
```eval_rst
|
||||
.. note::
|
||||
By default, the ``groupmanagement.request_groups`` permission is applied to the ``Member`` group. In most instances this, and perhaps adding it to the ``Blue`` group, should be all that is ever needed. It is unsupported and NOT advisable to apply this permission to a public group. See #697 for more information.
|
||||
```
|
||||
|
||||
# Group Management
|
||||
|
||||
In order to access group management, users need to be either a superuser, granted the `auth | user | group_management ( Access to add members to groups within the alliance )` permission or a group leader (discussed later).
|
||||
|
||||
## Group Requests
|
||||
|
||||
When a user joins or leaves a group which is not marked as "Open", their group request will have to be approved manually by a user with the `group_management` permission or by a group leader of the group they are requesting.
|
||||
|
||||
## Group Membership
|
||||
|
||||
The group membership tab gives an overview of all of the non-internal groups.
|
||||
|
||||

|
||||
|
||||
### Group Member Management
|
||||
|
||||
Clicking on the blue eye will take you to the group member management screen. Here you can see a list of people who are in the group, and remove members where necessary.
|
||||
|
||||

|
||||
|
||||
|
||||
## Group Leaders
|
||||
|
||||
Group leaders have the same abilities as users with the `group_management` permission, _however_, they will only be able to:
|
||||
|
||||
- Approve requests for groups they are a leader of.
|
||||
- View the Group Membership and Group Members of groups they are leaders of.
|
||||
|
||||
This allows you to more finely control who has access to manage which groups. Currently it is not possible to add a Group as group leaders.
|
||||
@@ -3,8 +3,10 @@
|
||||
```eval_rst
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: Contents
|
||||
:caption: Features Contents
|
||||
|
||||
hrapplications
|
||||
corpstats
|
||||
groups
|
||||
permissions_tool
|
||||
```
|
||||
|
||||
39
docs/features/permissions_tool.md
Normal file
@@ -0,0 +1,39 @@
|
||||
# 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.
|
||||
|
||||
### Access
|
||||
|
||||
In order to grant users access to the permissions auditing tool they will need to be granted the `permissions_tool.audit_permissions` permission or be a superuser.
|
||||
|
||||
When a user has access to the tool they will see the "Permissions Audit" menu item under the "Util" sub menu.
|
||||
|
||||
|
||||
### Permissions Overview
|
||||
|
||||
The first page gives you a general overview of permissions and how many users have access to each permission.
|
||||
|
||||

|
||||
|
||||
**App**, **Model** and **Code Name** contain the internal details of the permission while **Name** contains the name/description you'll see in the admin panel.
|
||||
|
||||
**Users** is the number of users explicitly granted this permission on their account.
|
||||
|
||||
**Groups** is the number of groups with this permission assigned.
|
||||
|
||||
**Groups Users** is the total number of users in all of the groups with this permission assigned.
|
||||
|
||||
Clicking on the **Code Name** link will take you to the [Permissions Audit Page](#permissions-audit-page)
|
||||
|
||||
### Permissions Audit Page
|
||||
|
||||
The permissions audit page will give you an overview of all the users who have access to this permission either directly or granted via group membership.
|
||||
|
||||

|
||||
|
||||
Please note that users may appear multiple times if this permission is granted via multiple sources.
|
||||
@@ -11,44 +11,12 @@ For other distros, adapt the procedure and find distro-specific alternatives for
|
||||
|
||||
# Using
|
||||
|
||||
See the [Quick Start Guide](installation/auth/quickstart.md)
|
||||
See the [Quick Start Guide](installation/auth/quickstart.md) and learn about individual [features.](features/index.md)
|
||||
|
||||
# Troubleshooting
|
||||
|
||||
Read the [list of common problems.](maintenance/troubleshooting.md)
|
||||
|
||||
# Upgrading
|
||||
|
||||
As AllianceAuth is developed, new features are added while old bugs are repaired. It’s good practice to keep your instance of AllianceAuth up to date.
|
||||
|
||||
Some updates require specific instructions. Refer to their entry in the [changelog](maintenance/changelog.md)
|
||||
|
||||
In general, the update process has 4 steps:
|
||||
- download the latest code
|
||||
- generate new models in the database
|
||||
- update current models in the database
|
||||
- rebuild web cache
|
||||
|
||||
To perform each of these steps, you’ll need to be working from the console in the AllianceAuth directory. Usually `cd ~/allianceauth`
|
||||
|
||||
Start by pulling the code changes:
|
||||
|
||||
git pull
|
||||
|
||||
Modify settings.py according to the changelog.
|
||||
|
||||
For an automated upgrade, run the script:
|
||||
|
||||
bash update.sh
|
||||
|
||||
For a manual upgrade, execute the commands in this order:
|
||||
|
||||
sudo pip install -r requirements.txt
|
||||
|
||||
python manage.py migrate
|
||||
|
||||
python manage.py collectstatic
|
||||
|
||||
|
||||
```eval_rst
|
||||
.. toctree::
|
||||
|
||||
@@ -3,6 +3,10 @@
|
||||
|
||||
AllianceAuth gets served using a Web Server Gateway Interface (WSGI) script. This script passes web requests to AllianceAuth which generates the content to be displayed and returns it. This means very little has to be configured in Apache to host AllianceAuth.
|
||||
|
||||
In the interest of ~~laziness~~ time-efficiency, scroll down for example configs. Use these, changing the ServerName to your domain name.
|
||||
|
||||
If you're using a small VPS to host services with very limited memory resources, consider using NGINX with [Gunicorn](gunicorn.md). Even if you would like to use Apache, Gunicorn may give you lower memory usage over mod_wsgi.
|
||||
|
||||
### Required Parameters for AllianceAuth Core
|
||||
|
||||
The AllianceAuth core requires the following parameters to be set:
|
||||
@@ -50,6 +54,31 @@ You can supply your own SSL certificates if you so desire. The alternative is ru
|
||||
|
||||
## Sample Config Files
|
||||
|
||||
### Minimally functional config
|
||||
|
||||
```
|
||||
<VirtualHost *:80>
|
||||
ServerName example.com
|
||||
ServerAdmin webmaster@localhost
|
||||
|
||||
DocumentRoot /var/www
|
||||
|
||||
WSGIDaemonProcess allianceauth python-path=/home/allianceserver/allianceauth
|
||||
WSGIProcessGroup allianceauth
|
||||
WSGIScriptAlias / /home/allianceserver/allianceauth/alliance_auth/wsgi.py
|
||||
|
||||
Alias /static/ /home/allianceserver/allianceauth/static/
|
||||
|
||||
<Directory /home/allianceserver/allianceauth/>
|
||||
Require all granted
|
||||
</Directory>
|
||||
|
||||
<Directory /var/www/>
|
||||
Require all granted
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
```
|
||||
|
||||
### Own SSL Cert
|
||||
- Apache 2.4 or newer:
|
||||
- [000-default.conf](http://pastebin.com/3LLzyNmV)
|
||||
@@ -58,12 +87,14 @@ You can supply your own SSL certificates if you so desire. The alternative is ru
|
||||
- [000-default](http://pastebin.com/HfyKpQNu)
|
||||
- [default-ssl](http://pastebin.com/2WCS5jnb)
|
||||
|
||||
### No SSL or Cloudflare
|
||||
### No SSL Cloudflare, or LetsEncrypt
|
||||
- Apache 2.4 or newer:
|
||||
- [000-default.conf](http://pastebin.com/j1Ps3ZK6)
|
||||
- Apache 2.3 or older:
|
||||
- [000-default](http://pastebin.com/BHQzf2pj)
|
||||
|
||||
To have LetsEncrypt automatically install SSL certs, comment out the three lines starting with `WSGI`, install certificates, then uncomment them in `000-default-ls-ssl.conf`
|
||||
|
||||
## Enabling and Disabling Sites
|
||||
|
||||
To instruct apache to serve traffic from a virtual host, enable it:
|
||||
|
||||
@@ -5,9 +5,10 @@ It's recommended to update all packages before proceeding.
|
||||
`sudo yum upgrade`
|
||||
`sudo reboot`
|
||||
|
||||
Now install all [dependencies](dependencies.md). For this guide you'll need the optional [JDK](dependencies.md) and [Apache](dependencies.md) sections as well.
|
||||
Now install all [dependencies](dependencies.md).
|
||||
|
||||
sudo yum install xxxxxxx
|
||||
|
||||
replacing the x's with the list of packages.
|
||||
|
||||
Make sure redis is running before continuing:
|
||||
@@ -28,9 +29,9 @@ Find the line which says `root ALL=(ALL) ALL` - beneath it add another
|
||||
|
||||
**From this point on you need to be logged in as the allianceserver user**
|
||||
|
||||
start your mariadb server `sudo systemctl start mariadb`
|
||||
Start your mariadb server `sudo systemctl start mariadb`
|
||||
|
||||
secure your MYSQL / Maria-db server by typing `mysql_secure_installation `
|
||||
Secure your MYSQL / Maria-db server by typing `mysql_secure_installation `
|
||||
|
||||
AllianceAuth needs a MySQL user account. Create one as follows, replacing `PASSWORD` with an actual secure password:
|
||||
|
||||
@@ -50,10 +51,14 @@ Ensure you are in the allianceserver home directory by issuing `cd`
|
||||
|
||||
Now we clone the source code:
|
||||
|
||||
git clone https://github.com/R4stl1n/allianceauth.git
|
||||
git clone https://github.com/allianceauth/allianceauth.git
|
||||
|
||||
Enter the folder by issuing `cd allianceauth`
|
||||
|
||||
Ensure you're on the latest version with the following:
|
||||
|
||||
git tag | sort -n | tail -1 | xargs git checkout
|
||||
|
||||
Python package dependencies can be installed from the requirements file:
|
||||
|
||||
sudo pip install -r requirements.txt
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
# CloudFlare
|
||||
# Cloudflare
|
||||
|
||||
CloudFlare offers free SSL and DDOS mitigation services. Why not take advantage of it?
|
||||
|
||||
## Setup Protection
|
||||
## Setup
|
||||
|
||||
You’ll need to register an account on [CloudFlare’s site.](https://www.cloudflare.com/)
|
||||
|
||||
@@ -10,32 +10,26 @@ Along the top bar, select `Add Site`
|
||||
|
||||
Enter your domain name. It will scan records and let you know you can add the site. Continue setup.
|
||||
|
||||
On the next page you should see an A record for yourdomain.com pointing at your server IP. If not, manually add one:
|
||||
On the next page you should see an A record for example.com pointing at your server IP. If not, manually add one:
|
||||
|
||||
A yourdomain.com my.server.ip.address Automatic TTL
|
||||
A example.com my.server.ip.address Automatic TTL
|
||||
|
||||
Add the record and ensure the cloud under Status is orange. If not, click it. This ensures traffic gets screened by CloudFlare.
|
||||
|
||||
If you want forums or kb on a subdomain, and want these to be protected by CloudFlare, add an additional record for for each subdomain in the following format, ensuring the cloud is orange:
|
||||
|
||||
CNAME subdomain yourdomain.com Automatic TTL
|
||||
CNAME subdomain example.com Automatic TTL
|
||||
|
||||
CloudFlare blocks ports outside 80 and 443 on hosts it protects. This means, if the cloud is orange, only web traffic will get through. We need to reconfigure AllianceAuth to provide services under a subdomain. Configure these subdomains as above, but ensure the cloud is not orange (arrow should go around a grey cloud).
|
||||
|
||||
## Redirect to HTTPS
|
||||
|
||||
Now we need to configure the https redirect to force all traffic to https. Along the top bar of CloudFlare, select `Page Rules`. Add a new rule, Pattern is yourdomain.com, toggle the `Always use https` to ON, and save. It’ll take a few minutes to propagate.
|
||||
Now we need to configure the https redirect to force all traffic to https. Along the top bar of CloudFlare, select `Page Rules`. Add a new rule, Pattern is example.com, toggle the `Always use https` to ON, and save. It’ll take a few minutes to propagate.
|
||||
|
||||

|
||||
|
||||
## Update Auth URLs
|
||||
|
||||
Edit settings.py and change the following values:
|
||||
- FORUM_URL = `os.environ.get('AA_FORUM_URL', "http://forums.mydomain.com")` if forums are on a subdomain
|
||||
- IPBOARD_ENDPOINT = `os.environ.get('AA_IPBOARD_ENDPOINT', 'http://forums.mydomain.com/ipboard/interface/board/index.php')` if forums are on a subdomain
|
||||
- JABBER_URL = `os.environ.get('AA_JABBER_URL', "jabber.yourdomain.com")`
|
||||
- OPENFIRE_ADDRESS = `os.environ.get('AA_OPENFIRE_ADDRESS', "http://jabber.yourdomain.com:9090")`
|
||||
- MUMBLE_URL = `os.environ.get('AA_MUMBLE_URL', "mumble.yourdomain.com")`
|
||||
- TEAMSPEAK3_PUBLIC_URL = `os.environ.get('AA_TEAMSPEAK3_PUBLIC_URL', 'ts.yourdomain.com')`
|
||||
Edit settings.py and replace everything that has a HTTP with HTTPS (except anything with a port on the end, like `OPENFIRE_ADDRESS`)
|
||||
|
||||
And there we have it. You’re DDOS-protected with free SSL.
|
||||
And there we have it. You’re DDOS-protected with free SSL.
|
||||
@@ -31,6 +31,7 @@ Required for phpBB, smf, evernus alliance market, etc
|
||||
|
||||
### Java
|
||||
Required for hosting jabber server
|
||||
|
||||
oracle-java8-installer
|
||||
|
||||
## CentOS 7
|
||||
@@ -53,7 +54,7 @@ Required for base auth site
|
||||
|
||||
#### Utilities
|
||||
|
||||
screen gcc unzip git redis curl nano
|
||||
screen gcc gcc-c++ unzip git redis curl nano
|
||||
|
||||
### Apache
|
||||
Required for displaying web content
|
||||
|
||||
121
docs/installation/auth/gunicorn.md
Normal file
@@ -0,0 +1,121 @@
|
||||
# Gunicorn
|
||||
|
||||
[Gunicorn](http://gunicorn.org) is a Python WSGI HTTP Server for UNIX. The Gunicorn server is light on server resources, and fairly speedy.
|
||||
|
||||
If you find Apache's `mod_wsgi` to be a headache or want to use NGINX (or some other webserver), then Gunicorn could be for you. There are a number of other WSGI server options out there and this documentation should be enough for you to piece together how to get them working with your environment.
|
||||
|
||||
Check out the full [Gunicorn docs](http://docs.gunicorn.org/en/latest/index.html).
|
||||
|
||||
## Setting up Gunicorn
|
||||
|
||||
```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`.
|
||||
```
|
||||
|
||||
Install Gunicorn using pip, `pip install gunicorn`.
|
||||
|
||||
In your `allianceauth` base directory, try running `gunicorn --bind 0.0.0.0:8000 alliance_auth.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.
|
||||
|
||||
Once you validate its running, you can kill the process with Ctrl+C and continue.
|
||||
|
||||
## Running Gunicorn with Supervisor
|
||||
|
||||
You should use [Supervisor](supervisor.md) to keep all of Alliance Auth components running (instead of using screen). You don't _have to_ but we will be using it to start and run Gunicorn so you might as well.
|
||||
|
||||
### Sample Supervisor config
|
||||
You'll want to edit `/etc/supervisor/conf.d/aauth_gunicorn.conf` (or whatever you want to call the config file)
|
||||
```
|
||||
[program:aauth-gunicorn]
|
||||
user = www-data
|
||||
directory=/home/allianceserver/allianceauth/
|
||||
command=gunicorn alliance_auth.wsgi --workers=3 --timeout 120
|
||||
autostart=true
|
||||
autorestart=true
|
||||
stopsignal=INT
|
||||
```
|
||||
|
||||
- `[program:aauth-gunicorn]` - Change aauth-gunicorn to whatever you wish to call your process in Supervisor.
|
||||
- `user = www-data` - Change to whatever user you wish Gunicorn to run as. You could even set this as allianceserver if you wished. I'll leave the question security of that up to you.
|
||||
- `directory=/home/allianceserver/allianceauth/` - Needs to be the path to your Alliance Auth install.
|
||||
- `command=gunicorn alliance_auth.wsgi --workers=3 --timeout 120` - Running Gunicorn and the options to launch with. This is where you have some decisions to make, we'll continue below.
|
||||
|
||||
#### Gunicorn Arguments
|
||||
|
||||
See the [Commonly Used Arguments](http://docs.gunicorn.org/en/latest/run.html#commonly-used-arguments) or [Full list of settings](http://docs.gunicorn.org/en/stable/settings.html) for more information.
|
||||
|
||||
##### Where to bind Gunicorn to?
|
||||
What address are you going to use to reference it? By default, without a bind parameter, Gunicorn will bind to `127.0.0.1:8000`. This might be fine for your application. If it clashes with another application running on that port you will need to change it. I would suggest using UNIX sockets too, if you can.
|
||||
|
||||
For UNIX sockets add `--bind=unix:/run/allianceauth.sock` (or to a path you wish to use). Remember that your web server will need to be able to access this socket file.
|
||||
|
||||
For a TCP address add `--bind=127.0.0.1:8001` (or to the address/port you wish to use, but I would strongly advise against binding it to an external address).
|
||||
|
||||
Whatever you decide to use, remember it because we'll need it when configuring your webserver.
|
||||
|
||||
##### Number of workers
|
||||
By default Gunicorn will spawn only one worker. The number you set this to will depend on your own server environment, how many visitors you have etc. Gunicorn suggests between 2-4 workers per core. Really you could probably get away with 2-4 in total for most installs.
|
||||
|
||||
Change it by adding `--workers=2` to the command.
|
||||
|
||||
##### Running with a virtual environment
|
||||
If you're running with a virtual environment, you'll need to add the path to the `command=` config line.
|
||||
|
||||
e.g. `command=/path/to/venv/bin/gunicorn alliance_auth.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/`.
|
||||
|
||||
|
||||
## Configuring your webserver
|
||||
|
||||
### NGINX
|
||||
To your server config add:
|
||||
|
||||
```
|
||||
location / {
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
proxy_read_timeout 90;
|
||||
proxy_redirect http://127.0.0.1:8000/ http://$host/;
|
||||
proxy_set_header Host $host;
|
||||
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
|
||||
proxy_set_header X-Real-IP $remote_addr;
|
||||
proxy_set_header X-Forwarded-Proto $scheme;
|
||||
}
|
||||
```
|
||||
|
||||
Set `proxy_pass` and `proxy_redirect` to the address you set under `--bind=`. Set the second part of `proxy_redirect` to the URL you're hosting services on. Tell NGINX to reload your config, job done. Enjoy your lower memory usage and better performance!
|
||||
|
||||
If PHP is stopping you moving to NGINX, check out php-fpm as a way to run your PHP applications.
|
||||
|
||||
### Apache
|
||||
If you were using mod_wsgi before, make a backup of your old config first and then strip out all of the mod_wsgi config from your Apache VirtualHost first config.
|
||||
|
||||
Your config will need something along these lines:
|
||||
```
|
||||
ProxyPreserveHost On
|
||||
<Location />
|
||||
SSLRequireSSL
|
||||
ProxyPass http://127.0.0.1:8000/
|
||||
ProxyPassReverse http://127.0.0.1:8000/
|
||||
RequestHeader set X-FORWARDED-PROTOCOL ssl
|
||||
RequestHeader set X-FORWARDED-SSL on
|
||||
</Location>
|
||||
```
|
||||
|
||||
Set `ProxyPass` and `ProxyPassReverse` addresses to your `--bind=` address set earlier.
|
||||
|
||||
You will need to enable some Apache mods. `sudo a2enmod http_proxy` should take care of the dependencies.
|
||||
|
||||
Restart Apache and you should be done.
|
||||
|
||||
### Other web servers
|
||||
|
||||
Any web server capable of proxy passing should be able to sit in front of Gunicorn. Consult their documentation armed with your `--bind=` address and you should be able to find how to do it relatively easy.
|
||||
|
||||
|
||||
## 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 aauth-gunicorn`, or the service name you chose for it.
|
||||
@@ -7,7 +7,10 @@
|
||||
ubuntu
|
||||
centos
|
||||
settings
|
||||
nginx
|
||||
apache
|
||||
gunicorn
|
||||
cloudflare
|
||||
supervisor
|
||||
quickstart
|
||||
```
|
||||
|
||||
93
docs/installation/auth/nginx.md
Normal file
@@ -0,0 +1,93 @@
|
||||
# NGINX
|
||||
|
||||
## Overivew
|
||||
|
||||
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.
|
||||
|
||||
If you're trying to cram Alliance Auth into a very small VPS of say, 1-2GB or less, then Nginx will be considerably friendlier to your resources compared to Apache.
|
||||
|
||||
You can read more about NGINX on the [NGINX wiki](https://www.nginx.com/resources/wiki/).
|
||||
|
||||
## Coming from Apache
|
||||
|
||||
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.
|
||||
|
||||
```eval_rst
|
||||
+-----------+----------------------------------------+
|
||||
| Apache | Nginx Replacement |
|
||||
+===========+========================================+
|
||||
| mod_php | php5-fpm or php7-fpm (PHP FastCGI) |
|
||||
+-----------+----------------------------------------+
|
||||
| mod_wsgi | Gunicorn or other external WSGI server |
|
||||
+-----------+----------------------------------------+
|
||||
|
||||
```
|
||||
|
||||
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/)
|
||||
|
||||
## 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.
|
||||
|
||||
You will need to have [Gunicorn](gunicorn.md) or some other WSGI server setup for hosting Alliance Auth.
|
||||
|
||||
Create a config file in `/etc/nginx/sites-available` call it `alliance-auth.conf` or whatever your preferred name is and copy the basic config in. Make whatever changes you feel are necessary.
|
||||
|
||||
Create a symbolic link to enable the site `sudo ln -s /etc/nginx/sites-available/alliance-auth.conf /etc/nginx/sites-enabled/` and then reload Nginx for the config to take effect, `sudo service nginx reload` for Ubuntu.
|
||||
|
||||
### Basic config
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
location = /favicon.ico { access_log off; log_not_found off; }
|
||||
|
||||
location /static/ {
|
||||
alias /home/allianceserver/allianceauth/static/;
|
||||
autoindex off;
|
||||
}
|
||||
|
||||
# Gunicorn config goes below
|
||||
location / {
|
||||
include proxy_params;
|
||||
proxy_pass http://127.0.0.1:8000;
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### Adding TLS/SSL
|
||||
|
||||
With [Let's Encrypt](https://letsencrypt.org/) offering free SSL certificates, there's no good reason to not run HTTPS anymore.
|
||||
|
||||
Your config will need a few additions once you've got your certificate.
|
||||
|
||||
```
|
||||
listen 443 ssl http2; # Replace listen 80; with this
|
||||
|
||||
ssl_certificate /path/to/your/cert.crt;
|
||||
ssl_certificate_key /path/to/your/cert.key;
|
||||
|
||||
ssl on;
|
||||
ssl_session_cache builtin:1000 shared:SSL:10m;
|
||||
ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
|
||||
ssl_ciphers EECDH+ECDSA+AESGCM:EECDH+aRSA+AESGCM:EECDH+ECDSA+SHA384:EECDH+ECDSA+SHA256:EECDH+aRSA+SHA384:EECDH+aRSA+SHA256:EECDH+aRSA+RC4:EECDH:EDH+aRSA:RC4:!aNULL:!eNULL:!LOW:!3DES:!MD5:!EXP:!PSK:!SRP:!DSS;
|
||||
ssl_prefer_server_ciphers on;
|
||||
```
|
||||
|
||||
If you want to redirect all your non-SSL visitors to your secure site, below your main configs `server` block, add the following:
|
||||
|
||||
```
|
||||
server {
|
||||
listen 80;
|
||||
server_name example.com;
|
||||
|
||||
# Redirect all HTTP requests to HTTPS with a 301 Moved Permanently response.
|
||||
return 301 https://$host$request_uri;
|
||||
}
|
||||
```
|
||||
|
||||
If you have trouble with the `ssl_ciphers` listed here or some other part of the SSL config, try getting the values from [Mozilla's SSL Config Generator](https://mozilla.github.io/server-side-tls/ssl-config-generator/).
|
||||
@@ -4,11 +4,11 @@ Once you’ve installed AllianceAuth, perform these steps to get yourself up and
|
||||
|
||||
First you need a superuser account. You can use this as a personal account. From the command line, `python manage.py createsuperuser` and follow the prompts.
|
||||
|
||||
The big goal of AllianceAuth is the automation of group membership, so we’ll need some groups. In the admin interface, select `Groups`, then at the top-right select `Add Group`. Give it a name and select permissions. Special characters (including spaces) are removing before syncing to services, so try not to have group names which will be the same upon cleaning. A description of permissions can be found in the [readme file](https://github.com/R4stl1n/allianceauth/blob/master/README.md). Repeat for all the groups you see fit, whenever you need a new one.
|
||||
The big goal of AllianceAuth is the automation of group membership, so we’ll need some groups. In the admin interface, select `Groups`, then at the top-right select `Add Group`. Give it a name and select permissions. Special characters (including spaces) are removing before syncing to services, so try not to have group names which will be the same upon cleaning. A description of permissions can be found in the [readme file](https://github.com/allianceauth/allianceauth/blob/master/README.md). Repeat for all the groups you see fit, whenever you need a new one.
|
||||
|
||||
### Background Processes
|
||||
|
||||
To start the background processes to sync groups and check api keys, issue these commands:
|
||||
|
||||
screen -dm bash -c 'python manage.py celeryd'
|
||||
screen -dm bash -c 'python manage.py celerybeat'
|
||||
screen -dm bash -c 'celery -A alliance_auth worker'
|
||||
screen -dm bash -c 'celery -A alliance_auth beat'
|
||||
|
||||
@@ -19,145 +19,130 @@ When changing these booleans, edit the setting within the brackets (eg `('AA_MEM
|
||||
# Fields to Modify
|
||||
|
||||
## Required
|
||||
- [SECRET_KEY](#secret_key)
|
||||
- [SECRET_KEY](#secret-key)
|
||||
- Use [this tool](http://www.miniwebtool.com/django-secret-key-generator/) to generate a key on initial install
|
||||
- [DEBUG](#debug)
|
||||
- If issues are encountered, set this to `True` to view a more detailed error report, otherwise set `False`
|
||||
- [ALLOWED_HOSTS](#allowed_hosts)
|
||||
- [ALLOWED_HOSTS](#allowed-hosts)
|
||||
- This restricts web addresses auth will answer to. Separate with commas.
|
||||
- Should include localhost `127.0.0.1` and `yourdomain.com`
|
||||
- Should include localhost `127.0.0.1` and `example.com`
|
||||
- To allow from all, include `'*'`
|
||||
- [DATABASES](#databases)
|
||||
- Fill out the database name and user credentials to manage the auth database.
|
||||
- [DOMAIN](#domain)
|
||||
- Set to the domain name AllianceAuth will be accessible under
|
||||
- [EMAIL_HOST_USER](#email_host_user)
|
||||
- [EMAIL_HOST_USER](#email-host-user)
|
||||
- Username to send emails from. If gmail account, the full gmail address.
|
||||
- [EMAIL_HOST_PASSWORD](#email_host_password)
|
||||
- [EMAIL_HOST_PASSWORD](#email-host-password)
|
||||
- Password for the email user.
|
||||
- [CORP_IDS](#corp_ids)
|
||||
- [CORP_IDS](#corp-ids)
|
||||
- List of corp IDs who are members. Exclude if their alliance is in `ALLIANCE_IDS`
|
||||
- [ALLIANCE_IDS](#alliance_ids)
|
||||
- [ALLIANCE_IDS](#alliance-ids)
|
||||
- List of alliance IDs who are members.
|
||||
- [ESI_SSO_CLIENT_ID](#esi_sso_client_id)
|
||||
- EVE application ID from the developers site. See the [SSO Configuration Instruction](#ESI_SSO_CLIENT_ID)
|
||||
- [ESI_SSO_CLIENT_SECRET](#esi_sso_client_secret)
|
||||
- [ESI_SSO_CLIENT_ID](#esi-sso-client_id)
|
||||
- EVE application ID from the developers site. See the [SSO Configuration Instruction](#sso-settings)
|
||||
- [ESI_SSO_CLIENT_SECRET](#esi-sso-client-secret)
|
||||
- EVE application secret from the developers site.
|
||||
- [ESI_SSO_CALLBACK_URL](#esi_sso_callback_url)
|
||||
- [ESI_SSO_CALLBACK_URL](#esi-sso-callback-url)
|
||||
- OAuth callback URL. Should be `https://mydomain.com/sso/callback`
|
||||
|
||||
## Services
|
||||
### Member Services
|
||||
After installing services, enable specific services for members by setting the following to `True`
|
||||
- [ENABLE_AUTH_FORUM](#enable_auth_forum)
|
||||
- [ENABLE_AUTH_JABBER](#enable_auth_jabber)
|
||||
- [ENABLE_AUTH_MUMBLE](#enable_auth_mumble)
|
||||
- [ENABLE_AUTH_IPBOARD](#enable_auth_ipboard)
|
||||
- [ENABLE_AUTH_TEAMSPEAK3](#enable_auth_teamspeak3)
|
||||
- [ENABLE_AUTH_DISCORD](#enable_auth_discord)
|
||||
- [ENABLE_AUTH_DISCOURSE](#enable_auth_discourse)
|
||||
- [ENABLE_AUTH_IPS4](#enable_auth_ips4)
|
||||
- [ENABLE_AUTH_SMF](#enable_auth_smf)
|
||||
- [ENABLE_AUTH_MARKET](#enable_auth_market)
|
||||
- [ENABLE_AUTH_XENFORO](#enable_auth_xenforo)
|
||||
|
||||
### Blue Services
|
||||
After installing services, enable specific services for blues by setting the following to `True`
|
||||
- [ENABLE_BLUE_FORUM](#enable_blue_forum)
|
||||
- [ENABLE_BLUE_JABBER](#enable_blue_jabber)
|
||||
- [ENABLE_BLUE_MUMBLE](#enable_blue_mumble)
|
||||
- [ENABLE_BLUE_IPBOARD](#enable_blue_ipboard)
|
||||
- [ENABLE_BLUE_TEAMSPEAK3](#enable_blue_teamspeak3)
|
||||
- [ENABLE_BLUE_DISCORD](#enable_blue_discord)
|
||||
- [ENABLE_BLUE_DISCOURSE](#enable_blue_discourse)
|
||||
- [ENABLE_BLUE_IPS4](#enable_blue_ips4)
|
||||
- [ENABLE_BLUE_SMF](#enable_blue_smf)
|
||||
- [ENABLE_BLUE_MARKET](#enable_blue_market)
|
||||
- [ENABLE_BLUE_XENFORO](#enable_blue_xenforo)
|
||||
|
||||
### IPBoard
|
||||
If using IPBoard, the following need to be set
|
||||
- [IPBOARD_ENDPOINT](#ipboard_endpoint)
|
||||
- [IPBOARD_APIKEY](#ipboard_apikey)
|
||||
- [IPBOARD_APIMODULE](#ipboard_apimodule)
|
||||
If using IPBoard, the following need to be set in accordance with the [install instructions](../services/ipboard3.md)
|
||||
- [IPBOARD_ENDPOINT](#ipboard-endpoint)
|
||||
- [IPBOARD_APIKEY](#ipboard-apikey)
|
||||
- [IPBOARD_APIMODULE](#ipboard-apimodule)
|
||||
|
||||
### XenForo
|
||||
If using XenForo, the following need to be set
|
||||
- [XENFORO_ENDPOINT](#xenforo_endpoint)
|
||||
- [XENFORO_APIKEY](#xenforo_apikey)
|
||||
If using XenForo, the following need to be set in accordance with the [install instructions](../services/xenforo.md)
|
||||
- [XENFORO_ENDPOINT](#xenforo-endpoint)
|
||||
- [XENFORO_APIKEY](#xenforo-apikey)
|
||||
|
||||
### Openfire
|
||||
If using Openfire, the following need to be set
|
||||
- [JABBER_URL](#jabber_url)
|
||||
- [JABBER_PORT](#jabber_port)
|
||||
- [JABBER_SERVER](#jabber_server)
|
||||
- [OPENFIRE_ADDRESS](#openfire_address)
|
||||
- [OPENFIRE_SECRET_KEY](#openfire_secret_key)
|
||||
- [BROADCAST_USER](#broadcast_user)
|
||||
- [BROADCAST_USER_PASSWORD](#broadcast_user_password)
|
||||
- [BROADCAST_SERVICE_NAME](#broadcast_service_name)
|
||||
If using Openfire, the following need to be set in accordance with the [install instructions](../services/openfire.md)
|
||||
- [JABBER_URL](#jabber-url)
|
||||
- [JABBER_PORT](#jabber-port)
|
||||
- [JABBER_SERVER](#jabber-server)
|
||||
- [OPENFIRE_ADDRESS](#openfire-address)
|
||||
- [OPENFIRE_SECRET_KEY](#openfire-secret-key)
|
||||
- [BROADCAST_USER](#broadcast-user)
|
||||
- [BROADCAST_USER_PASSWORD](#broadcast-user-password)
|
||||
- [BROADCAST_SERVICE_NAME](#broadcast-service-name)
|
||||
- [BROADCAST_IGNORE_INVALID_CERT](#broadcast-ignore-invalid-cert)
|
||||
|
||||
### Mumble
|
||||
If using Mumble, the following need to be set
|
||||
- [MUMBLE_URL](#mumble_url)
|
||||
If using Mumble, the following needs to be set to the address of the mumble server:
|
||||
- [MUMBLE_URL](#mumble-url)
|
||||
|
||||
### PHPBB3
|
||||
If using phpBB3, the database needs to be defined.
|
||||
|
||||
### Teamspeak3
|
||||
If using Teamspeak3, the following need to be set
|
||||
- [TEAMSPEAK3_SERVER_IP](#teamspeak3_server_ip)
|
||||
- [TEAMSPEAK3_SERVER_PORT](#teamspeak3_server_port)
|
||||
- [TEAMSPEAK3_SERVERQUERY_USER](#teamspeak3_serverquery_user)
|
||||
- [TEAMSPEAK3_SERVERQUERY_PASSWORD](#teamspeak3_serverquery_password)
|
||||
- [TEAMSPEAK3_VIRTUAL_SERVER](#teamspeak3_virtual_server)
|
||||
- [TEAMSPEAK3_PUBLIC_URL](#teamspeak3_public_url)
|
||||
If using Teamspeak3, the following need to be set in accordance with the [install instrictions](../services/teamspeak3.md)
|
||||
- [TEAMSPEAK3_SERVER_IP](#teamspeak3-server-ip)
|
||||
- [TEAMSPEAK3_SERVER_PORT](#teamspeak3-server-port)
|
||||
- [TEAMSPEAK3_SERVERQUERY_USER](#teamspeak3-serverquery-user)
|
||||
- [TEAMSPEAK3_SERVERQUERY_PASSWORD](#teamspeak3-serverquery-password)
|
||||
- [TEAMSPEAK3_VIRTUAL_SERVER](#teamspeak3-virtual-server)
|
||||
- [TEAMSPEAK3_PUBLIC_URL](#teamspeak3-public-url)
|
||||
|
||||
### Discord
|
||||
If connecting to a Discord server, set the following
|
||||
- [DISCORD_SERVER_ID](#discord_server_id)
|
||||
- [DISCORD_USER_EMAIL](#discord_user_email)
|
||||
- [DISCORD_USER_PASSWORD](#discord_user_password)
|
||||
If connecting to a Discord server, set the following in accordance with the [install instructions](../services/discord.md)
|
||||
- [DISCORD_GUILD_ID](#discord-guild-id)
|
||||
- [DISCORD_BOT_TOKEN](#discord-bot-token)
|
||||
- [DISCORD_INVITE_CODE](#discord-invite-code)
|
||||
- [DISCORD_APP_ID](#discord-app-id)
|
||||
- [DISCORD_APP_SECRET](#discord-app-secret)
|
||||
- [DISCORD_CALLBACK_URL](#discord-callback-url)
|
||||
- [DISCORD_SYNC_NAMES](#discord-sync-names)
|
||||
|
||||
### Discourse
|
||||
If connecting to Discourse, set the following
|
||||
- [DISCOURSE_URL](#discourse_url)
|
||||
- [DISCOURSE_API_USERNAME](#discourse_api_username)
|
||||
- [DISCOURSE_API_KEY](#discourse_api_key)
|
||||
- [DISCOURSE_SSO_SECRET](#discourse_sso_secret)
|
||||
If connecting to Discourse, set the following in accordance with the [install instructions](../services/discourse.md)
|
||||
- [DISCOURSE_URL](#discourse-url)
|
||||
- [DISCOURSE_API_USERNAME](#discourse-api-username)
|
||||
- [DISCOURSE_API_KEY](#discourse-api-key)
|
||||
- [DISCOURSE_SSO_SECRET](#discourse-sso-secret)
|
||||
|
||||
### IPSuite4
|
||||
If using IPSuite4 (aka IPBoard4) the following are required:
|
||||
- [IPS4_URL](#ips4_url)
|
||||
- [IPS4_URL](#ips4-url)
|
||||
- the database needs to be defined
|
||||
|
||||
### SMF
|
||||
If using SMF the following are required:
|
||||
- [SMF_URL](#smf_url)
|
||||
- [SMF_URL](#smf-url)
|
||||
- the database needs to be defined
|
||||
|
||||
## Optional
|
||||
### Standings
|
||||
To allow access to blues, a corp API key is required to pull standings from. Corp does not need to be owning corp or in owning alliance. Required mask is 16 (Communications/ContactList)
|
||||
- [CORP_API_ID](#corp_api_id)
|
||||
- [CORP_API_VCODE](#corp_api_vcode)
|
||||
- [CORP_API_ID](#corp-api-id)
|
||||
- [CORP_API_VCODE](#corp-api-vcode)
|
||||
|
||||
### Jacknife
|
||||
To view APIs on a different Jacknife install, set [JACK_KNIFE_URL](#jack_knife_url)
|
||||
### API Key Audit URL
|
||||
To define what happens when an API is clicked, set according to [these instructions](#hr-configuration)
|
||||
- [API_KEY_AUDIT_URL](#api-key-audit-url)
|
||||
|
||||
### Auto Groups
|
||||
Groups can be automatically assigned based on a user's corp or alliance. Set the following to `True` to enable this feature.
|
||||
- [MEMBER_CORP_GROUPS](#member_corp_groups)
|
||||
- [MEMBER_ALLIANCE_GROUPS](#member_alliance_groups)
|
||||
- [BLUE_CORP_GROUPS](#blue_corp_groups)
|
||||
- [BLUE_ALLIANCE_GROUPS](#blue_alliance_groups)
|
||||
- [MEMBER_CORP_GROUPS](#member-corp-groups)
|
||||
- [MEMBER_ALLIANCE_GROUPS](#member-alliance-groups)
|
||||
- [BLUE_CORP_GROUPS](#blue-corp-groups)
|
||||
- [BLUE_ALLIANCE_GROUPS](#blue-alliance-groups)
|
||||
|
||||
### Fleet-Up
|
||||
Fittings and operations can be imported from Fleet-Up. Define the following to do so.
|
||||
- [FLEETUP_APP_KEY](#fleetup_app_key)
|
||||
- [FLEETUP_USER_ID](#fleetup_user_id)
|
||||
- [FLEETUP_API_ID](#fleetup_api_id)
|
||||
- [FLEETUP_GROUP_ID](#fleetup_group_id)
|
||||
- [FLEETUP_APP_KEY](#fleetup-app-key)
|
||||
- [FLEETUP_USER_ID](#fleetup-user-id)
|
||||
- [FLEETUP_API_ID](#fleetup-api-id)
|
||||
- [FLEETUP_GROUP_ID](#fleetup-group-id)
|
||||
|
||||
### CAPTCHA
|
||||
To help prevent bots from registering and brute forcing the login. Get the reCaptcha keys from [here](https://www.google.com/recaptcha/intro/index.html)
|
||||
- [CAPTCHA_ENABLED](#captcha_enabled)
|
||||
- [RECAPTCHA_PUBLIC_KEY](#recaptcha_public_key)
|
||||
- [RECAPTCHA_PRIVATE_KEY](#recaptcha_private_key)
|
||||
- [NOCAPTCHA](#nocaptcha)
|
||||
|
||||
# Description of Settings
|
||||
## Django
|
||||
@@ -166,19 +151,27 @@ A random string used in cryptographic functions, such as password hashing. Chang
|
||||
### DEBUG
|
||||
Replaces the generic `SERVER ERROR (500)` page when an error is encountered with a page containing a traceback and variables. May expose sensitive information so not recommended for production.
|
||||
### ALLOWED_HOSTS
|
||||
A list of addresses used to validate headers: AllianceAuth will block connection coming from any other address unless `DEBUG` is `True`. This should be a list of URLs and IPs to allow. For instance, include 'mydomain.com', 'www.mydomain.com', and the server's IP address to ensure connections will be accepted.
|
||||
A list of addresses used to validate headers: AllianceAuth will block connection coming from any other address. This should be a list of URLs and IPs to allow. In most cases, just adding `'example.com'` is sufficient. This also accepts the `'*'` wildcard for testing purposes.
|
||||
### DATABASES
|
||||
List of databases available. Contains the Django database, and may include service ones if enabled. Service databases are defined in their individual sections and appended as needed automatically.
|
||||
### LANGUAGE_CODE
|
||||
Friendly name of the local language.
|
||||
### TIME_ZONE
|
||||
Friendly name of the local timezone.
|
||||
### CAPTCHA_ENABLED
|
||||
Enable Google reCaptcha
|
||||
### RECAPTCHA_PUBLIC_KEY
|
||||
Google reCaptcha public key
|
||||
### RECAPTCHA_PRIVATE_KEY
|
||||
Google reCaptcha private key
|
||||
### NOCAPTCHA
|
||||
Enable New No Captcha reCaptcha
|
||||
### STATIC_URL
|
||||
Absolute URL to serve static files from.
|
||||
### STATIC_ROOT
|
||||
Root folder to store static files in.
|
||||
### SUPERUSER_STATE_BYPASS
|
||||
Overrides superuser account states to always return True on membership tests. If issues are encountered, or you want to test access to certain portions of the site, set to False to disable.
|
||||
Overrides superuser account states to always return True on membership tests. If issues are encountered, or you want to test access to certain portions of the site, set to False to respect true states of superusers.
|
||||
## EMAIL SETTINGS
|
||||
### DOMAIN
|
||||
The URL to which emails will link.
|
||||
@@ -208,7 +201,7 @@ The application cliend ID generated from the [developers site.](https://develope
|
||||
### ESI_SSO_CLIENT_SECRET
|
||||
The application secret key generated from the [developers site.](https://developers.eveonline.com)
|
||||
### ESI_SSO_CALLBACK_URL
|
||||
The callback URL for authentication handshake. Should be `https://mydomain.com/sso/callback`.
|
||||
The callback URL for authentication handshake. Should be `https://example.com/sso/callback`.
|
||||
## Default Group Settings
|
||||
### DEFAULT_AUTH_GROUP
|
||||
Name of the group members of the owning corp or alliance are put in.
|
||||
@@ -222,52 +215,6 @@ If `True`, add members to groups with their alliance name, prefixed with `Allian
|
||||
If `True`, add blues to groups with their corp name, prefixed with `Corp_`
|
||||
### BLUE_ALLIANCE_GROUPS
|
||||
If `True`, add blues to groups with their alliance name, prefixed with `Alliance_`
|
||||
## Alliance Service Setup
|
||||
### ENABLE_AUTH_FORUM
|
||||
Allow members of the owning corp or alliance to generate accounts on a Phpbb3 install.
|
||||
### ENABLE_AUTH_JABBER
|
||||
Allow members of the owning corp or alliance to generate accounts on an Openfire install.
|
||||
### ENABLE_AUTH_MUMBLE
|
||||
Allow members of the owning corp or alliance to generate accounts on a Mumble install.
|
||||
### ENABLE_AUTH_IPBOARD
|
||||
Allow members of the owning corp or alliance to generate accounts on an IPBoard install.
|
||||
### ENABLE_AUTH_TEAMSPEAK3
|
||||
Allow members of the owning corp or alliance to generate accounts on a Teamspeak3 install.
|
||||
### ENABLE_AUTH_DISCORD
|
||||
Allow members of the owning corp or alliance to link accounts to a Discord server.
|
||||
### ENABLE_AUTH_DISCOURSE
|
||||
Allow members of the owning corp or alliance to generate accounts on a Discourse install
|
||||
### ENABLE_AUTH_IPS4
|
||||
Allow members of the owning corp or alliance to generate accounts on a IPSuite4 install.
|
||||
### ENABLE_AUTH_SMF
|
||||
Allow members of the owning corp or alliance to generate accounts on a SMF install.
|
||||
### ENABLE_AUTH_MARKET
|
||||
Allow members of the owning corp or alliance to generate accounts on an alliance market install.
|
||||
### ENABLE_AUTH_XENFORO
|
||||
Allow members of the owning corp or alliance to generate accounts on a XenForo install.
|
||||
## Blue Service Setup
|
||||
### ENABLE_BLUE_FORUM
|
||||
Allow blues of the owning corp or alliance to generate accounts on a Phpbb3 install.
|
||||
### ENABLE_BLUE_JABBER
|
||||
Allow blues of the owning corp or alliance to generate accounts on an Openfire install.
|
||||
### ENABLE_BLUE_MUMBLE
|
||||
Allow blues of the owning corp or alliance to generate accounts on a Mumble install.
|
||||
### ENABLE_BLUE_IPBOARD
|
||||
Allow blues of the owning corp or alliance to generate accounts on an IPBoard install.
|
||||
### ENABLE_BLUE_TEAMSPEAK3
|
||||
Allow blues of the owning corp or alliance to generate accounts on a Teamspeak3 install.
|
||||
### ENABLE_BLUE_DISCORD
|
||||
Allow blues of the owning corp or alliance to link accounts to a Discord server.
|
||||
### ENABLE_BLUE_DISCOURSE
|
||||
Allow blues of the owning corp or alliance to generate accounts on a Discourse install.
|
||||
### ENABLE_BLUE_IPS4
|
||||
Allow blues of the owning corp or alliance to generate accounts on an IPSuite4 install.
|
||||
### ENABLE_BLUE_SMF
|
||||
Allow blues of the owning corp or alliance to generate accounts on a SMF install.
|
||||
### ENABLE_BLUE_MARKET
|
||||
Allow blues of the owning corp or alliance to generate accounts on an alliance market install.
|
||||
### ENABLE_BLUE_XENFORO
|
||||
Allow blues of the owning corp or alliance to generate accounts on a XenForo install.
|
||||
## Tenant Configuration
|
||||
Characters of any corp or alliance with their ID here will be treated as a member.
|
||||
### CORP_IDS
|
||||
@@ -310,14 +257,22 @@ The default data source to get character information. Default is `esi`
|
||||
The default data source to get corporation information. Default is `esi`
|
||||
### EVEONLINE_ALLIANCE_PROVIDER
|
||||
The default data source to get alliance information. Default is `esi`
|
||||
### EVEONLINE_ITEMTYPE_PROVIDER
|
||||
The default data source to get item type information. Default is `esi`
|
||||
## Alliance Market
|
||||
### MARKET_URL
|
||||
The web address to access the Evernus Alliance Market application.
|
||||
### MARKET_DB
|
||||
The Evernus Alliance Market database connection information.
|
||||
## HR Configuration
|
||||
### JACK_KNIFE_URL
|
||||
Link to an install of [eve-jackknife](https://code.google.com/archive/p/eve-jackknife/)
|
||||
### API_KEY_AUDIT_URL
|
||||
This setting defines what happens when someone clicks on an API key (such as in corpstats or an application).
|
||||
|
||||
Default behaviour is to show the verification code in a popup, but this can be set to link out to a website.
|
||||
|
||||
The URL set here uses python string formatting notation. Variable names are enclosed in `{}` brackets. Three variable names are available: `api_id`, `vcode`, and `pk` (which is the primary key of the API in the database - only useful on the admin site).
|
||||
|
||||
Example URL structures are provided. Jacknife can be installed on your server following [its setup guide.](../services/jacknife.md)
|
||||
## IPBoard3 Configuration
|
||||
### IPBOARD_ENDPOINT
|
||||
URL to the `index.php` file of a IPBoard install's API server.
|
||||
@@ -327,20 +282,20 @@ API key for accessing an IPBoard install's API
|
||||
Module to access while using the API
|
||||
## XenForo Configuration
|
||||
### XENFORO_ENDPOINT
|
||||
The address of the XenForo API. Should look like `https://mydomain.com/forum/api.php`
|
||||
The address of the XenForo API. Should look like `https://example.com/forum/api.php`
|
||||
### XENFORO_DEFAULT_GROUP
|
||||
The group ID of the group to assign to member. Default is 0.
|
||||
### XENFORO_APIKEY
|
||||
The API key generated from XenForo to allow API access.
|
||||
## Jabber Configuration
|
||||
### JABBER_URL
|
||||
Address to instruct members to connect their jabber clients to, in order to reach an Openfire install. Usually just `mydomain.com`
|
||||
Address to instruct members to connect their jabber clients to, in order to reach an Openfire install. Usually just `example.com`
|
||||
### JABBER_PORT
|
||||
Port to instruct members to connect their jabber clients to, in order to reach an Openfire install. Usually 5223.
|
||||
### JABBER_SERVER
|
||||
Server name of an Openfire install. Usually `mydomain.com`
|
||||
Server name of an Openfire install. Usually `example.com`
|
||||
### OPENFIRE_ADDRESS
|
||||
URL of the admin web interface for an Openfire install. Usually `http://mydomain.com:9090`. If HTTPS is desired, change port to 9091: `https://mydomain.com:9091`
|
||||
URL of the admin web interface for an Openfire install. Usually `http://example.com:9090`. If HTTPS is desired, change port to 9091: `https://example.com:9091`
|
||||
### OPENFIRE_SECRET_KEY
|
||||
Secret key used to authenticate with an Openfire admin interface.
|
||||
### BROADCAST_USER
|
||||
@@ -366,7 +321,7 @@ Password to use when authenticating as the `TEAMSPEAK3_SERVERQUERY_USER`. Provid
|
||||
### TEAMSPEAK3_VIRTUAL_SERVER
|
||||
ID of the server on which to manage users. Usually `1`.
|
||||
### TEAMSPEAK3_PUBLIC_URL
|
||||
Address to instruct members to connect their Teamspeak3 clients to. Usually `mydomain.com`
|
||||
Address to instruct members to connect their Teamspeak3 clients to. Usually `example.com`
|
||||
## Discord Configuration
|
||||
### DISCORD_GUILD_ID
|
||||
The ID of a Discord server on which to manage users.
|
||||
@@ -379,7 +334,7 @@ The application ID obtained from defining an application on the [Discord develop
|
||||
### DISCORD_APP_SECRET
|
||||
The application secret key obtained from defining an application on the [Discord developers site.](https://discordapp.com/developers/applications/me)
|
||||
### DISCORD_CALLBACK_URL
|
||||
The callback URL used for authenticaiton flow. Should be `https://mydomain.com/discord_callback`. Must match exactly the one used when defining the application.
|
||||
The callback URL used for authenticaiton flow. Should be `https://example.com/discord_callback`. Must match exactly the one used when defining the application.
|
||||
### DISCORD_SYNC_NAMES
|
||||
Override usernames on the server to match the user's main character.
|
||||
## Discourse Configuration
|
||||
@@ -417,4 +372,5 @@ This section is used to manage how logging messages are processed.
|
||||
|
||||
To turn off logging notifications, change the `handlers` `notifications` `class` to `logging.NullHandler`
|
||||
|
||||
## Everything below logging is magic. Do Not Touch
|
||||
## Danger Zone
|
||||
Everything below logging is magic. **Do not touch.**
|
||||
|
||||
@@ -15,14 +15,14 @@ Ubuntu:
|
||||
CentOS:
|
||||
|
||||
sudo yum install supervisor
|
||||
sudo systemctl enable supervisor.service
|
||||
sudo systemctl start supervisor.service
|
||||
sudo systemctl enable supervisord.service
|
||||
sudo systemctl start supervisord.service
|
||||
|
||||
## Configuration
|
||||
|
||||
Auth provides example config files for the celery workers (celeryd), the periodic task scheduler (celerybeat), and the mumble authenticator. All of these are available in `thirdparty/Supervisor`.
|
||||
Auth provides example config files for the celery workers, the periodic task scheduler (celery beat), and the mumble authenticator. All of these are available in `thirdparty/Supervisor`.
|
||||
|
||||
For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth-celerybeat.conf` and `auth-celeryd.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard:
|
||||
For most users, all you have to do is copy the config files to `/etc/supervisor/conf.d` then restart the service. Copy `auth.conf` for the celery workers, and `auth-mumble.conf` for the mumble authenticator. For all three just use a wildcard:
|
||||
|
||||
sudo cp thirdparty/Supervisor/* /etc/supervisor/conf.d
|
||||
|
||||
@@ -41,15 +41,33 @@ To ensure the processes are working, check their status:
|
||||
sudo supervisorctl status
|
||||
|
||||
Processes will be `STARTING`, `RUNNING`, or `ERROR`. If an error has occurred, check their log files:
|
||||
- celeryd: `log/worker.log`
|
||||
- celerybeat: `log/beat.log`
|
||||
- celery workers: `log/worker.log`
|
||||
- celery beat: `log/beat.log`
|
||||
- authenticator: `log/authenticator.log`
|
||||
|
||||
## Restarting Processes
|
||||
|
||||
To restart the celery group:
|
||||
|
||||
sudo supervisorctl restart auth:*
|
||||
|
||||
To restart just celerybeat:
|
||||
|
||||
sudo supervisorctl restart auth:celerybeat
|
||||
|
||||
To restart just celeryd:
|
||||
|
||||
sudo supervisorctl restart auth:celeryd
|
||||
|
||||
To restart just mumble authenticator:
|
||||
|
||||
sudo supervisorctl restart auth-mumble
|
||||
|
||||
## Customizing Config Files
|
||||
|
||||
The only real customization needed is if running in a virtual environment. The python path will have to be changed in order to start in the venv.
|
||||
|
||||
Edit the config files and find the line saying `command`. Replace `python` with `/path/to/venv/python`. This can be relative to the `directory` specified in the config file.
|
||||
Edit the config files and find the line saying `command`. Replace `python` with `/path/to/venv/bin/python`. For Celery replace `celery` with `/path/to/venv/bin/celery`. This can be relative to the `directory` specified in the config file.
|
||||
|
||||
Note that for config changes to be loaded, the supervisor service must be restarted.
|
||||
|
||||
@@ -60,4 +78,4 @@ Most often this is caused by a permissions issue on the allianceauth directory (
|
||||
|
||||
### Workers are using old settings
|
||||
|
||||
Every time the codebase is updated or settings file changed, workers will have to be restarted. Easiest way is to restart the supervisor service (see configuration above for commands)
|
||||
Every time the codebase is updated or settings file changed, workers will have to be restarted. Easiest way is to restart the supervisor service (see configuration above for commands)
|
||||
|
||||
@@ -40,13 +40,16 @@ Ensure you are in the allianceserver home directory by issuing `cd`
|
||||
|
||||
Now we clone the source code:
|
||||
|
||||
git clone https://github.com/R4stl1n/allianceauth.git
|
||||
git clone https://github.com/allianceauth/allianceauth.git
|
||||
|
||||
Enter the folder by issuing `cd allianceauth`
|
||||
|
||||
Ensure you're on the latest version with the following:
|
||||
|
||||
git tag | sort -n | tail -1 | xargs git checkout
|
||||
|
||||
Python package dependencies can be installed from the requirements file:
|
||||
|
||||
sudo pip install requests>=2.9.1
|
||||
sudo pip install -r requirements.txt
|
||||
|
||||
The settings file needs configuring. See [this lengthy guide](settings.md) for specifics.
|
||||
|
||||
@@ -3,6 +3,9 @@
|
||||
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.
|
||||
|
||||
## Setup
|
||||
|
||||
Add `services.modules.discord` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
### Creating a Server
|
||||
*If you already have a Discord server, skip the creation step, but be sure to retrieve the server ID and enter it in settings.py*
|
||||
|
||||
@@ -29,7 +32,7 @@ This returns a code that looks like `https://discord.gg/0fmA8MyXV6qt7XAZ`. The p
|
||||
|
||||
Navigate to the [Discord Developers site.](https://discordapp.com/developers/applications/me) Press the plus sign to create a new application.
|
||||
|
||||
Give it a name and description relating to your auth site. Add a redirect to `https://mydomain.com/discord_callback`, substituting your domain. Press Create Application.
|
||||
Give it a name and description relating to your auth site. Add a redirect to `https://example.com/discord/callback/`, substituting your domain. Press Create Application.
|
||||
|
||||
Update settings.py, inputting this redirect address as `DISCORD_CALLBACK_URL`
|
||||
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Discourse
|
||||
|
||||
Add `services.modules.discourse` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Install Docker
|
||||
|
||||
wget -qO- https://get.docker.io/ | sh
|
||||
@@ -71,7 +74,7 @@ Discourse must run on its own subdomain - it can't handle routing behind an alia
|
||||
And enter the following, changing the port if you used a different number:
|
||||
|
||||
<VirtualHost *:80>
|
||||
ServerName discourse.mydomain.com
|
||||
ServerName discourse.example.com
|
||||
ProxyPass / http://0.0.0.0:7890/
|
||||
ProxyPassReverse / http://0.0.0.0:7890/
|
||||
</VirtualHost>
|
||||
@@ -94,22 +97,22 @@ Follow prompts, being sure to answer `y` when asked to allow admin privileges.
|
||||
|
||||
### Create API key
|
||||
|
||||
Navigate to `discourse.mydomain.com` and log on. Top right press the 3 lines and select `Admin`. Go to API tab and press `Generate Master API Key`.
|
||||
Navigate to `discourse.example.com` and log on. Top right press the 3 lines and select `Admin`. Go to API tab and press `Generate Master API Key`.
|
||||
|
||||
Now go to the allianceauth folder and edit settings:
|
||||
|
||||
nano /home/allianceserver/allianceauth/alliance_auth/settings.py
|
||||
|
||||
Scroll down to the Discourse section and set the following:
|
||||
- `DISCOURSE_URL`: `discourse.mydomain.com`
|
||||
- `DISCOURSE_URL`: `discourse.example.com`
|
||||
- `DISCOURSE_API_USERNAME`: the username of the admin account you generated the API key with
|
||||
- `DISCOURSE_API_KEY`: the key you just generated
|
||||
|
||||
### Configure SSO
|
||||
|
||||
Navigate to `discourse.mydomain.com` and log in. Back to the admin site, scroll down to find SSO settings and set the following:
|
||||
Navigate to `discourse.example.com` and log in. Back to the admin site, scroll down to find SSO settings and set the following:
|
||||
- `enable_sso`: True
|
||||
- `sso_url`: `http://mydomain.com/discourse_sso`
|
||||
- `sso_url`: `http://example.com/discourse/sso`
|
||||
- `sso_secret`: some secure key
|
||||
|
||||
Save, now change settings.py and add the following:
|
||||
|
||||
@@ -3,6 +3,7 @@
|
||||
```eval_rst
|
||||
.. toctree::
|
||||
|
||||
permissions
|
||||
market
|
||||
discord
|
||||
discourse
|
||||
@@ -13,5 +14,7 @@
|
||||
smf
|
||||
teamspeak3
|
||||
xenforo
|
||||
jacknife
|
||||
pathfinder
|
||||
|
||||
```
|
||||
|
||||
@@ -1,8 +1,6 @@
|
||||
# IPBoard3
|
||||
|
||||
Yes, you read that right. AllianceAuth only supports IPBoard 3, not the new shiny 4. Why? Because InvisionPower removed the API we used to manage it.
|
||||
|
||||
Moving right along.
|
||||
Add `services.modules.ipboard` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
You’re on your own for the initial install of IPBoard. It’s pretty much just download, unzip, and move to `/var/www/ipboard/`. Make sure to
|
||||
|
||||
@@ -10,7 +8,7 @@ You’re on your own for the initial install of IPBoard. It’s pretty much just
|
||||
|
||||
a few times because it’s pretty finicky.
|
||||
|
||||
You’ll need to add another alias in your [apache config](https://github.com/R4stl1n/allianceauth/wiki/Apache-Setup#additional-parameters-for-full-setup), this one for `/ipboard/` pointing to `/var/www/ipboard` and add another `<directory>` block for `/var/www/ipboard` with `Require all granted` or `Allow from all` depending on your apache version.
|
||||
You’ll need to add another alias in your apache config, this one for `/ipboard` pointing to `/var/www/ipboard` and add another `<directory>` block for `/var/www/ipboard` with `Require all granted` or `Allow from all` depending on your apache version.
|
||||
|
||||
IPBoard needs a database table. Log in to mysql and run:
|
||||
|
||||
@@ -18,7 +16,7 @@ IPBoard needs a database table. Log in to mysql and run:
|
||||
|
||||
That’s all for SQL work. Control+D to close.
|
||||
|
||||
Navigate to http://yourdomain.com/ipboard and proceed with the install. If it whines about permissions make sure to `chown` again. Point it at that database we just made, using the `allianceserver` MySQL user account from the full install.
|
||||
Navigate to http://example.com/ipboard and proceed with the install. If it whines about permissions make sure to `chown` again. Point it at that database we just made, using the `allianceserver` MySQL user account from the full install.
|
||||
|
||||
Once you get everything installed we need to copy the api module folder
|
||||
|
||||
@@ -33,12 +31,12 @@ Enable the API by toggling the `XML-RPC Status` from `disabled` to `enabled` (re
|
||||
Copy the API key. Now edit your settings.py as follows:
|
||||
|
||||
- IPBOARD_APIKEY is the key you just copied
|
||||
- IPBOARD_ENDPOINT is `http://yourdomain.com/ipboard/interface/board/index.php`
|
||||
- IPBOARD_ENDPOINT is `http://example.com/ipboard/interface/board/index.php`
|
||||
|
||||
Now enable IPBoard for Auth and/or Blue by editing the [booleans](#alliance-service-setup).
|
||||
Now enable IPBoard for Auth and/or Blue by editing the auth settings.
|
||||
|
||||
Save and exit. Restart apache or gunicorn.
|
||||
|
||||
Test it by creating a user through AllianceAuth. Just note right now there’s no real error handling, so if account creation fails it’ll still return a username/password combo.
|
||||
Test it by creating a user through Alliance Auth. Just note right now there’s no real error handling, so if account creation fails it’ll still return a username/password combo.
|
||||
|
||||
Good luck!
|
||||
|
||||
71
docs/installation/services/jacknife.md
Normal file
@@ -0,0 +1,71 @@
|
||||
# Eve Jacknife
|
||||
|
||||
## Overview
|
||||
Eve Jacknife is used to audit an api so that you might see character skills and what ships they can fly, mails, contracts,assets, and any other given access from a specific api key.
|
||||
|
||||
## Dependencies
|
||||
All php and mysql dependencies should have been taken care of during setup.
|
||||
|
||||
## Installation
|
||||
### Get Code
|
||||
Navigate to your server's web directory: `cd /var/www`
|
||||
|
||||
Download the code: `sudo git clone https://github.com/whinis/eve-jacknife`
|
||||
|
||||
### Create Database
|
||||
|
||||
mysql -u root -p -e "create database jackknife; grant all privileges on jackknife.* to 'allianceserver'@'localhost';"
|
||||
|
||||
### Configure Settings
|
||||
|
||||
Change directory to jacknife: `cd eve-jacknife`
|
||||
|
||||
Now copy the template: `sudo cp base.config.php eve.config.php`
|
||||
|
||||
And now edit: `sudo nano eve.config.php`
|
||||
|
||||
Add the database user information:
|
||||
- `$sql_u = "allianceserver"`
|
||||
- `$sql_p = "MY_SQL_PASSWORD_HERE"`
|
||||
|
||||
## Apache Configuration
|
||||
|
||||
Change ownership of the directory: `sudo chown -R www-data:www-data ../eve-jacknife`
|
||||
|
||||
Eve Jacknife can be served two ways: on its own subdomain (`jacknife.example.com`) or as an alias (`example.com/jacknife`)
|
||||
|
||||
### Subdomain
|
||||
As its own subdomain, create a new apache config: `sudo nano /etc/apache2/sites-available/jacknife.conf` and enter the following:
|
||||
|
||||
<VirtualHost *:80>
|
||||
DocumentRoot "/var/www/eve-jacknife"
|
||||
ServerName jacknife.example.com
|
||||
<Directory "/var/www/eve-jacknife">
|
||||
Require all granted
|
||||
AllowOverride all
|
||||
DirectoryIndex index.php
|
||||
</Directory>
|
||||
</VirtualHost>
|
||||
|
||||
Enable the new site with `sudo a2ensite jacknife.conf` and then reload apache with `sudo service apache2 reload`
|
||||
|
||||
### Alias
|
||||
As an alias, edit your site config (usually 000-default): `sudo nano etc/apache2/sites-available/000-default.conf` and add the following inside the `VirtualHost` block:
|
||||
|
||||
Alias /jacknife "/var/www/eve-jacknife/"
|
||||
<Directory "/var/www/eve-jacknife">
|
||||
Require all granted
|
||||
DirectoryIndex index.php
|
||||
</Directory>
|
||||
|
||||
Reload apache to take effect: `sudo service apache2 reload`
|
||||
|
||||
## Install SQL
|
||||
|
||||
Once apache is configured, Eve Jacknife needs to install some data. Navigate to it in your browser and append `/Installer.php` to the URL.
|
||||
|
||||
Enter your database password and press Check. If all the boxes come back green press Save. On the next screen press Install and wait for it to finish.
|
||||
|
||||
## Update Auth Settings
|
||||
|
||||
Edit your aut settings file (`nano ~/allianceauth/alliance_auth/settings.py`) and replace `API_KEY_AUDIT_URL` with either `http://jacknife.example.com/?usid={api_id}&apik={vcode}` or `http://example.com/jacknife/?usid={api_id}&apik={vcode}` depending on your apache choice.
|
||||
@@ -1,5 +1,7 @@
|
||||
# Alliance Market
|
||||
|
||||
Add `services.modules.market` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
Alliance Market needs a database. Create one in mysql. Default name is `alliance_market`:
|
||||
|
||||
mysql -u root -p
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
# Mumble
|
||||
|
||||
Add `services.modules.mumble` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## 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.
|
||||
|
||||
@@ -11,6 +14,10 @@ The mumble server package can be retrieved from a repository we need to add, mum
|
||||
Now two packages need to be installed:
|
||||
|
||||
sudo apt-get install python-software-properties mumble-server
|
||||
|
||||
You will also need to install the python dependencies for the authenticator script:
|
||||
|
||||
pip install -r thirdparty/Mumble/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:
|
||||
@@ -21,7 +28,7 @@ REQUIRED: To enable the ICE authenticator, edit the following:
|
||||
|
||||
- `icesecretwrite=MY_CLEVER_PASSWORD`, obviously choosing a secure password
|
||||
|
||||
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`
|
||||
@@ -30,7 +37,7 @@ To customize the database, edit the following:
|
||||
- `dbPort=3306`
|
||||
- `dbPrefix=murmur_`
|
||||
|
||||
To name your root channel, uncomment and edit `registerName=` whatever cool name you want
|
||||
To name your root channel, uncomment and set `registerName=` to whatever cool name you want
|
||||
|
||||
Save and close the file (control + O, control + X).
|
||||
|
||||
@@ -44,7 +51,7 @@ Now restart the server to see the changes reflected.
|
||||
|
||||
sudo service mumble-server restart
|
||||
|
||||
That’s it! Your server is ready to be connected to at yourdomain.com:64738
|
||||
That’s it! Your server is ready to be connected to at example.com:64738
|
||||
|
||||
## Configuring the Authenticator
|
||||
|
||||
@@ -64,7 +71,7 @@ Edit `authenticator.ini` and change these values:
|
||||
|
||||
Test your configuration by starting it: `python authenticator.py`
|
||||
|
||||
#Running the Authenticator
|
||||
## Running the Authenticator
|
||||
|
||||
The authenticator needs to be running 24/7 to validate users on Mumble. The best way is to run it in a screen much like celery:
|
||||
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# Openfire
|
||||
|
||||
Add `services.modules.openfire` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Overview
|
||||
Openfire is a java-based xmpp server (jabber).
|
||||
|
||||
@@ -14,7 +16,7 @@ One additional package is required - [openjdk8](http://askubuntu.com/questions/4
|
||||
### Download Installer
|
||||
Openfire is not available through repositories so we need to get a debian 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 in list, ends with .deb).
|
||||
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).
|
||||
|
||||
Retrieve the file location by copying the url from the “click here” link.
|
||||
|
||||
@@ -22,18 +24,18 @@ 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_3.10.2_all.deb
|
||||
wget https://www.igniterealtime.org/downloadServlet?filename=openfire/openfire_4.1.1_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)
|
||||
|
||||
sudo dpkg -i openfire_3.10.2_all.deb
|
||||
sudo dpkg -i openfire_4.1.1_all.deb
|
||||
|
||||
### Web Configuration
|
||||
The remainder of the setup occurs through Openfire’s web interface. Navigate to http://yourdomain.com:9090, or if you’re behind CloudFlare, go straight to your server’s IP:9090.
|
||||
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.
|
||||
|
||||
Under Server Settings, set the Domain to `yourdomain.com` replacing it with your actual domain. Don’t touch the rest.
|
||||
Under Server Settings, set the Domain to `example.com` replacing it with your actual domain. Don’t touch the rest.
|
||||
|
||||
Under Database Settings, select `Standard Database Connection`
|
||||
|
||||
@@ -58,7 +60,7 @@ Once loaded, press the green plus on the right for `REST API`.
|
||||
|
||||
Navigate the `Server` tab, `Sever Settings` subtab. At the bottom of the left navigation bar select `REST API`.
|
||||
|
||||
Select `Enabled`, and `Secret Key Auth`. Enter the secret key from OPENFIRE_SECRET_KEY here.
|
||||
Select `Enabled`, and `Secret Key Auth`. Update Alliance Auth settings with this secret key as `OPENFIRE_SECRET_KEY`.
|
||||
|
||||
### Broadcast Plugin Setup
|
||||
|
||||
@@ -78,8 +80,10 @@ Navigate to the `Server` tab, `Server Manager` subtab, and select `System Proper
|
||||
- Value: `True`
|
||||
- Do not encrypt this property value
|
||||
- Name: `plugin.broadcast.allowedUsers`
|
||||
- Value: `broadcast@yourdomain.com`, replacing the domain name with yours
|
||||
- Value: `broadcast@example.com`, replacing the domain name with yours
|
||||
- Do not encrypt this property value
|
||||
|
||||
If you have troubles getting broadcasts to work, you can try setting the optional (you will need to add it) `BROADCAST_IGNORE_INVALID_CERT` setting to `True`. This will allow invalid certificates to be used when connecting to the Openfire server to send a broadcast.
|
||||
|
||||
### Group Chat
|
||||
Channels are available which function like a chat room. Access can be controlled either by password or ACL (not unlike mumble).
|
||||
|
||||
77
docs/installation/services/pathfinder.md
Normal file
@@ -0,0 +1,77 @@
|
||||
# Pathfinder
|
||||
Pathfinder is a wormhole mapping tool.
|
||||
|
||||
While auth doesn't integrate with pathfinder anymore, from personal experience I've found it much easier to use the following install process than to try and follow the pathfinder-supplied docs.
|
||||
|
||||
|
||||
## Installation
|
||||
### Get the code
|
||||
|
||||
Navigate to the install location: `cd /var/www/` and git clone the repo:
|
||||
|
||||
sudo git clone https://github.com/exodus4d/pathfinder.git
|
||||
|
||||
### Create logs and caches
|
||||
|
||||
Change directory to pathfinder: `cd pathfinder`
|
||||
|
||||
The logging and caching folders need to be created and have permission set. If upon installation you get Server Error 500, try resetting these permissions.
|
||||
|
||||
sudo mkdir logs
|
||||
sudo mkdir tmp/cache
|
||||
sudo chmod -R 766 logs
|
||||
sudo chmod -R 766 tmp/cache
|
||||
|
||||
## .htaccess Configuration
|
||||
|
||||
In your `pathfinder` directory there are two `.htaccess` files. The default installation instructions want you to choose one for rewriting purposes, and these force you to www.pathfinder.example.com. Personally I don't like that.
|
||||
|
||||
So we'll frankenstein our own. We'll use the HTTP one as a base:
|
||||
|
||||
sudo mv .htaccess .htaccess_HTTPS
|
||||
sudo mv .htaccess_HTTPS .htaccess
|
||||
sudo nano .htaccess
|
||||
|
||||
Find the www rewriting section (labelled `Rewrite NONE www. to force www.`). Change it so that all lines start with a `#`:
|
||||
|
||||
#RewriteCond %{HTTP_HOST} !^www\.
|
||||
# skip "localhost" (dev environment)...
|
||||
#RewriteCond %{HTTP_HOST} !=localhost
|
||||
# skip IP calls (dev environment) e.g. 127.0.0.1
|
||||
#RewriteCond %{HTTP_HOST} !^[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}\.[0-9]{1,3}$
|
||||
# rewrite everything else to "http://" and "www."
|
||||
#RewriteRule .* http://www.%{HTTP_HOST}%{REQUEST_URI} [L,R=301]
|
||||
|
||||
This allows us to choose SSL and www forwarding with our apache conf instead of this htaccess file.
|
||||
|
||||
## Apache Configuration
|
||||
The best way to have this is to setup a subdomain on your server.
|
||||
|
||||
Create a config file `sudo nano /etc/apache2/sites-available/pathfinder.conf` and enter [this configuration](http://pastebin.com/wmXyf6pN), being sure to edit the `ServerName`
|
||||
|
||||
Enable it with:
|
||||
|
||||
sudo a2ensite pathfinder.conf
|
||||
sudo service apache2 reload
|
||||
|
||||
## Configuration Files
|
||||
|
||||
The default configuration should be fine in most cases. Edit all values with caution!
|
||||
|
||||
environment.ini
|
||||
- `SERVER` Should be changed to `PRODUCTION`
|
||||
- `BASE` is the full filesystem path to the application root on your server. In our case, `/var/www/pathfinder/`
|
||||
- `URL` Is the URL to your app (without a trailing slash). In our case, `http://pathfinder.example.com`
|
||||
- `DEBUG` sets the level of debugging (1,2 or 3) (check /logs for a more detail backtrace information)
|
||||
- `DB_*` sets your DB connection information
|
||||
- `SMTP_*` are used to send out emails, you will need an SMTP server login to make this work. (not required)
|
||||
- `SSO_CCP_*` follow the [official docs](https://github.com/exodus4d/pathfinder/wiki/CREST)
|
||||
|
||||
## Database Setup
|
||||
This is done through the browser. Go to `pathfinder.example.com/setup` and see the [official docs](https://github.com/exodus4d/pathfinder/wiki/Database) for instructions.
|
||||
|
||||
## Cron Jobs
|
||||
Again the [official docs](https://github.com/exodus4d/pathfinder/wiki/Cronjob) do a good job here.
|
||||
|
||||
## Finish Setup
|
||||
Once you've compelted the above steps, we need to disable the setup page. Edit the routes with `nano app/routes.ini` and put a `;` in front of the line starting with `GET @setup`
|
||||
37
docs/installation/services/permissions.md
Normal file
@@ -0,0 +1,37 @@
|
||||
# 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.
|
||||
|
||||
```eval_rst
|
||||
.. important::
|
||||
If you grant access to an individual user, they will have access to that service regardless of whether or not they are a member.
|
||||
```
|
||||
|
||||
Each service has an access permission defined, named like `Can access the <service name> service`.
|
||||
|
||||
To mimick the old behaviour of enabling services for all members, you would select the `Member` group from the admin panel, add the required service permission to the group and save. Likewise for Blues, select the `Blue` group and add the required permission.
|
||||
|
||||
A user can be granted the same permission from multiple sources. e.g. they may have it granted by several groups and directly granted on their account as well. Auth will not remove their account until all instances of the permission for that service have been revoked.
|
||||
|
||||
## Removing access
|
||||
|
||||
```eval_rst
|
||||
.. danger::
|
||||
Access removal is processed immediately after removing a permission from a user or group. If you remove access from a large group, such as Member, it will immediately remove all users from that service.
|
||||
```
|
||||
|
||||
When you remove a service permission from a user, a signal is triggered which will activate an immediate permission check. For users this will trigger an access check for all services. For groups, due to the potential extra load, only the services whose permissions have changed will be verified, and only the users in that group.
|
||||
|
||||
If a user no longer has permission to access the service when this permissions check is triggered, that service will be immediately disabled for them.
|
||||
|
||||
### 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.
|
||||
@@ -1,5 +1,7 @@
|
||||
# phpBB3
|
||||
|
||||
Add `services.modules.phpbb3` to your `INSTALLED_APPS` list 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.
|
||||
|
||||
@@ -14,11 +16,11 @@ 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
|
||||
|
||||
wget https://www.phpbb.com/files/release/phpBB-3.1.6.zip
|
||||
wget https://www.phpbb.com/files/release/phpBB-3.2.0.zip
|
||||
|
||||
This needs to be unpackaged. Unzip it, replacing the file name with that of the file you just downloaded
|
||||
|
||||
unzip phpBB-3.1.6.zip
|
||||
unzip phpBB-3.2.0.zip
|
||||
|
||||
Now we need to move this to our web directory. Usually `/var/www/forums`.
|
||||
|
||||
@@ -29,7 +31,7 @@ The web server needs read/write permission to this folder
|
||||
sudo chown -R www-data:www-data /var/www/forums
|
||||
|
||||
### Web Install
|
||||
Navigate to http://yourdomain.com/forums where you will be presented with an installer.
|
||||
Navigate to http://example.com/forums where you will be presented with an installer.
|
||||
|
||||
Click on the `Install` tab.
|
||||
|
||||
@@ -47,7 +49,7 @@ You should see `Succesful Connection` and proceed.
|
||||
|
||||
Enter administrator credentials on the next page.
|
||||
|
||||
Everything from hereon out should be intuitive.
|
||||
Everything from here should be intuitive.
|
||||
|
||||
phpBB will then write its own config file.
|
||||
|
||||
@@ -59,11 +61,11 @@ Before users can see the forums, we need to remove the install directory
|
||||
### 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`.
|
||||
|
||||
[Screenshot of this page](http://imgur.com/UOgaq6J)
|
||||

|
||||
|
||||
You can allow members to overwrite the portrait with a custom image if desired. Navigate to `Users and Groups`, `Group Permissions`, select the appropriate group (usually `Member` if you want everyone to have this ability), expand `Advanced Permissions`, under the `Profile` tab, set `Can Change Avatars` to `Yes`, and press `Apply Permissions`.
|
||||
|
||||
[Screenshot of this page](http://i.imgur.com/VGHwdxM.png)
|
||||

|
||||
|
||||
## Setup Complete
|
||||
You’ve finished the steps required to make AllianceAuth work with phpBB. Play around with it and make it your own.
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
# SMF
|
||||
|
||||
Add `services.modules.smf` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Overview
|
||||
SMF is a free php-based forum. It’s the one of the forums for AllianceAuth.
|
||||
|
||||
@@ -14,11 +16,11 @@ 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
|
||||
|
||||
wget http://download.simplemachines.org/index.php?thanks;filename=smf_2-0-11_install.zip
|
||||
wget http://download.simplemachines.org/index.php?thanks;filename=smf_2-0-13_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-11_install.zip
|
||||
unzip smf_2-0-13_install.zip
|
||||
|
||||
Now we need to move this to our web directory. Usually `/var/www/forums`.
|
||||
|
||||
@@ -29,7 +31,7 @@ The web server needs read/write permission to this folder
|
||||
sudo chown -R www-data:www-data /var/www/forums
|
||||
|
||||
### Web Install
|
||||
Navigate to http://yourdomain.com/forums where you will be presented with an installer.
|
||||
Navigate to http://example.com/forums where you will be presented with an installer.
|
||||
|
||||
Click on the `Install` tab.
|
||||
|
||||
|
||||
@@ -1,12 +1,18 @@
|
||||
# Teamspeak 3
|
||||
|
||||
Add `services.modules.teamspeak3` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
## Overview
|
||||
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.
|
||||
|
||||
## Dependencies
|
||||
All dependencies should have been taken care of during the AllianceAuth install.
|
||||
|
||||
## Setup
|
||||
Sticking with it? Alright, I tried.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -14,11 +20,11 @@ From the console, ensure you’re in the user’s home directory: `cd ~`
|
||||
|
||||
And now download the server, replacing the link with the link you got earlier.
|
||||
|
||||
wget http://dl.4players.de/ts/releases/3.0.11.4/teamspeak3-server_linux-amd64-3.0.11.4.tar.gz
|
||||
http://dl.4players.de/ts/releases/3.0.13.6/teamspeak3-server_linux_amd64-3.0.13.6.tar.bz2
|
||||
|
||||
Now we need to extract the file.
|
||||
|
||||
tar -xvf teamspeak3-server_linux-amd64-3.0.11.4.tar.gz
|
||||
tar -xf teamspeak3-server_linux_amd64-3.0.13.6.tar.bz2
|
||||
|
||||
### Create User
|
||||
Teamspeak needs its own user.
|
||||
@@ -28,15 +34,13 @@ Teamspeak needs its own user.
|
||||
### 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 mv teamspeak3-server_linux_amd64 /usr/local/teamspeak
|
||||
sudo 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
|
||||
|
||||
Finally we start the server.
|
||||
@@ -46,14 +50,16 @@ Finally we start the server.
|
||||
### Update Settings
|
||||
The console will spit out a block of text. **SAVE THIS**.
|
||||
|
||||
Update the AllianceAuth settings file with the following:
|
||||
- TEAMSPEAK3_SERVERQUERY_USER is `loginname`
|
||||
- TEAMSPEAK3_SERVERQUERY_PASSWORD is `password`
|
||||
Update the AllianceAuth settings file with the following from that block of text:
|
||||
- `TEAMSPEAK3_SERVERQUERY_USER` is `loginname` (usually `serveradmin`)
|
||||
- `TEAMSPEAK3_SERVERQUERY_PASSWORD` is `password`
|
||||
|
||||
Save and reload apache.
|
||||
Save and reload apache. Restart celery workers as well.
|
||||
|
||||
sudo service apache2 reload
|
||||
|
||||
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.
|
||||
|
||||
### 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.
|
||||
|
||||
@@ -63,8 +69,49 @@ Click the URL provided to automatically connect to our server. It will prompt yo
|
||||
|
||||
Now we need to make groups. AllianceAuth handles groups in teamspeak differently: instead of creating groups it creates an association between groups in TeamSpeak and groups in AllianceAuth. Go ahead and make the groups you want to associate with auth groups, keeping in mind multiple TeamSpeak groups can be associated with a single auth group.
|
||||
|
||||
Navigate back to the AllianceAuth admin interface (yourdomain.com/admin) and under `Services`, select `Auth / TS Groups`. In the top-right corner click `Add`.
|
||||
Navigate back to the AllianceAuth admin interface (example.com/admin) and under `Services`, select `Auth / TS Groups`. In the top-right corner click `Add`.
|
||||
|
||||
The dropdown box provides all auth groups. Select one and assign TeamSpeak groups from the panels below. If these panels are empty, wait a minute for the database update to run.
|
||||
The dropdown box provides all auth groups. Select one and assign TeamSpeak groups from the panels below. If these panels are empty, wait a minute for the database update to run, or see the [troubleshooting section](#ts-group-models-not-populating-on-admin-site) below.
|
||||
|
||||
## Setup Complete
|
||||
## Troubleshooting
|
||||
|
||||
### `Insufficient client permissions (failed on Invalid permission: 0x26)`
|
||||
|
||||
Using the advanced permissions editor, ensure the `Guest` group has the permission `Use Privilege Keys to gain permissions` (under `Virtual Server` expand the `Administration` section)
|
||||
|
||||
To enable advanced permissions, on your client go to the `Tools` menu, `Application`, and under the `Misc` section, tick `Advanced permission system`
|
||||
|
||||
### TS group models not populating on admin site
|
||||
The method which populates these runs every 30 minutes. To populate manually, start a celery shell:
|
||||
|
||||
celery -A alliance_auth shell
|
||||
|
||||
And execute the update:
|
||||
|
||||
run_ts3_group_update()
|
||||
|
||||
Ensure that command does not return an error.
|
||||
|
||||
### `2564 access to default group is forbidden`
|
||||
|
||||
This usually occurs because auth is trying to remove a user from the `Guest` group (group ID 8). The guest group is only assigned to a user when they have no other groups, unless you have changed the default teamspeak server config.
|
||||
|
||||
Teamspeak servers v3.0.13 and up are especially susceptible to this. Ensure the Channel Admin Group is not set to `Guest (8)`. Check by right clicking on the server name, `Edit virtual server`, and in the middle of the panel select the `Misc` tab.
|
||||
|
||||
### `TypeError: string indices must be integers, not str`
|
||||
|
||||
This error generally means teamspeak returned an error message that went unhandled. The full traceback is required for proper debugging, which the logs do not record. Please check the superuser notifications for this record and get in touch with a developer.
|
||||
|
||||
### `3331 flood ban`
|
||||
|
||||
This most commonly happens when your teamspeak server is externally hosted. You need to add the auth server IP to the teamspeak serverquery whitelist. This varies by provider.
|
||||
|
||||
If you have SSH access to the server hosting it, you need to locate the teamspeak server folder and add the auth server IP on a new line in `server_query_whitelist.txt`
|
||||
|
||||
### `520 invalid loginname or password`
|
||||
|
||||
The serverquery account login specified in settings.py is incorrect. Please verify `TEAMSPEAK3_SERVERQUERY_USER` and `TEAMSPEAK3_SERVERQUERY_PASSWORD`. The [installation section](#update-settings) describes where to get them.
|
||||
|
||||
### `2568 insufficient client permissions`
|
||||
|
||||
This usually occurs if you've created a separate serverquery user to use with auth. It has not been assigned sufficient permissions to complete all the tasks required of it. The full list of required permissions is not known, so assign liberally.
|
||||
@@ -1,5 +1,7 @@
|
||||
# XenForo
|
||||
|
||||
Add `services.modules.xenforo` to your `INSTALLED_APPS` list and run migrations before continuing with this guide to ensure the service is installed.
|
||||
|
||||
In this chapter we will explore how to setup AllianceAuth to work with [XenForo](https://xenforo.com/). At this point we will assume that you already have XenForo installed with a valid license (please keep in mind that XenForo is not free nor open-source, therefore you need to purchase a license first). If you come across any problems related with the installation of XenForo please contact their support service.
|
||||
|
||||
|
||||
|
||||
@@ -1,12 +1,12 @@
|
||||
# Changelog
|
||||
|
||||
## From now on all changelogs will be included as release notes.
|
||||
https://github.com/R4stl1n/allianceauth/releases
|
||||
https://github.com/allianceauth/allianceauth/releases
|
||||
|
||||
### 547
|
||||
Oct 16
|
||||
|
||||
Golly this is a big one. Upgrading takes a bit of work. [For full instructions click here.](https://github.com/R4stl1n/allianceauth/pull/547#issue-183247630)
|
||||
Golly this is a big one. Upgrading takes a bit of work. [For full instructions click here.](https://github.com/allianceauth/allianceauth/pull/547#issue-183247630)
|
||||
|
||||
- Update django version to 1.10
|
||||
- Remove member/blue permissions
|
||||
@@ -100,7 +100,7 @@ Mar 23 2016
|
||||
### 314
|
||||
Mar 22 2016
|
||||
- Revamp of the Human Resources Application and Management System
|
||||
- see the [wiki page](https://github.com/R4stl1n/allianceauth/wiki/HRApplications) for how to use the new system
|
||||
- see the [docs](../features/hrapplications.md) for how to use the new system
|
||||
- a completely untested conversion script exists. If you want to view your old models, contact Adarnof to try it out
|
||||
- Moved Error Handling for the API Keys to the API Calls to better handle API server outages
|
||||
- Removed the infamous database update task
|
||||
|
||||
@@ -6,6 +6,5 @@
|
||||
|
||||
changelog
|
||||
troubleshooting
|
||||
cloudflare
|
||||
|
||||
```
|
||||
|
||||
@@ -3,12 +3,11 @@
|
||||
## Something broken? Stuck on an issue? Can't get it set up?
|
||||
|
||||
Start here:
|
||||
- read the [documentation](https://github.com/R4stl1n/allianceauth/wiki)
|
||||
- check the [issues](https://github.com/R4stl1n/allianceauth/issues?utf8=%E2%9C%93&q=is%3Aissue) - especially closed ones
|
||||
- check the [issues](https://github.com/allianceauth/allianceauth/issues?utf8=%E2%9C%93&q=is%3Aissue) - especially closed ones
|
||||
- check the [forums](https://forums.eveonline.com/default.aspx?g=posts&t=383030)
|
||||
|
||||
No answer?
|
||||
- open an [issue](https://github.com/R4stl1n/allianceauth/issues)
|
||||
- open an [issue](https://github.com/allianceauth/allianceauth/issues)
|
||||
- harass us on [gitter](https://gitter.im/R4stl1n/allianceauth)
|
||||
- post to the [forums](https://forums.eveonline.com/default.aspx?g=posts&t=383030)
|
||||
|
||||
@@ -16,17 +15,19 @@ No answer?
|
||||
|
||||
### `pip install -r requirements.txt` is failing
|
||||
|
||||
Most commonly, your repositories did not include the `requests` package. Install it and try again: `sudo pip install requests`
|
||||
|
||||
Otherwise it's usually a missing dependency. Check [the list](../installation/auth/dependencies.md), reinstall, and try again.
|
||||
Either you need to `sudo` that command, or it's a missing dependency. Check [the list](../installation/auth/dependencies.md), reinstall, and try again.
|
||||
|
||||
### I'm getting an error 500 trying to connect to the website on a new install
|
||||
|
||||
Read the apache error log: `sudo nano /var/log/apache2/error.log`
|
||||
Read the apache error log: `sudo less /var/log/apache2/error.log`. Press Shift+G to go to the end of the file.
|
||||
|
||||
If it talks about failing to import something, google its name and install it.
|
||||
|
||||
If it whines about being unable to configure logger, make sure the log directory is write-able: `chmod -R 777 /home/allianceserver/allianceauth/log`, then reload apache.
|
||||
If it whines about being unable to configure logger, see below.
|
||||
|
||||
### Failed to configure log handler
|
||||
|
||||
Make sure the log directory is write-able: `chmod -R 777 /home/allianceserver/allianceauth/log`, then reload apache/celery/supervisor/etc.
|
||||
|
||||
### Groups aren't syncing to services
|
||||
|
||||
@@ -34,16 +35,10 @@ Make sure the background processes are running: `ps aux | grep celery` should re
|
||||
|
||||
If that doesn't do it, try clearing the worker queue. First kill all celery processes as described above, then do the following:
|
||||
|
||||
sudo rabbitmqctl stop_app
|
||||
sudo rabbitmqctl reset
|
||||
sudo rabbitmqctl start_app
|
||||
python manage.py celeryd --purge
|
||||
redis-cli FLUSHALL
|
||||
celery -A alliance_auth worker --purge
|
||||
|
||||
Press control+C once.
|
||||
|
||||
python manage.py celeryd --discard
|
||||
|
||||
Press control+C once.
|
||||
Press Control+C once.
|
||||
|
||||
Now start celery again with [these background process commands.](../installation/auth/quickstart.md)
|
||||
|
||||
|
||||
@@ -13,7 +13,17 @@ admin.site.register(EveCorporationInfo)
|
||||
|
||||
class EveApiKeyPairAdmin(admin.ModelAdmin):
|
||||
search_fields = ['api_id', 'user__username']
|
||||
list_display = ['api_id', 'user']
|
||||
list_display = ['api_id', 'user', 'characters']
|
||||
|
||||
@staticmethod
|
||||
def characters(obj):
|
||||
return ', '.join(sorted([c.character_name for c in EveCharacter.objects.filter(api_id=obj.api_id)]))
|
||||
|
||||
def get_search_results(self, request, queryset, search_term):
|
||||
queryset, use_distinct = super(EveApiKeyPairAdmin, self).get_search_results(request, queryset, search_term)
|
||||
chars = EveCharacter.objects.filter(character_name__icontains=search_term)
|
||||
queryset |= EveApiKeyPair.objects.filter(api_id__in=[char.api_id for char in chars if bool(char.api_id)])
|
||||
return queryset, use_distinct
|
||||
|
||||
|
||||
class EveCharacterAdmin(admin.ModelAdmin):
|
||||
|
||||
@@ -28,7 +28,10 @@ class UpdateKeyForm(forms.Form):
|
||||
raise forms.ValidationError("API ID must be a number")
|
||||
|
||||
def clean(self):
|
||||
super(UpdateKeyForm, self).clean()
|
||||
if 'api_id' not in self.cleaned_data or 'api_key' not in self.cleaned_data:
|
||||
# need to check if api_id and vcode in cleaned_data because
|
||||
# if they fail, they get removed from the dict but this method still happens
|
||||
return self.cleaned_data
|
||||
|
||||
if EveManager.check_if_api_key_pair_exist(self.cleaned_data['api_id']):
|
||||
logger.debug("UpdateKeyForm failed cleaning as API id %s already exists." % self.cleaned_data['api_id'])
|
||||
|
||||
@@ -3,39 +3,48 @@ from eveonline.models import EveCharacter
|
||||
from eveonline.models import EveApiKeyPair
|
||||
from eveonline.models import EveAllianceInfo
|
||||
from eveonline.models import EveCorporationInfo
|
||||
from authentication.models import AuthServicesInfo
|
||||
from eveonline.providers import eve_adapter_factory, EveXmlProvider
|
||||
from services.managers.eve_api_manager import EveApiManager
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
adapter = eve_adapter_factory()
|
||||
|
||||
class EveManager:
|
||||
def __init__(self):
|
||||
pass
|
||||
class EveManager(object):
|
||||
adapter = None
|
||||
|
||||
@classmethod
|
||||
def get_adapter(cls):
|
||||
if not cls.adapter:
|
||||
cls.adapter = eve_adapter_factory()
|
||||
return cls.adapter
|
||||
|
||||
@classmethod
|
||||
def get_character(cls, character_id):
|
||||
return cls.get_adapter().get_character(character_id)
|
||||
|
||||
@staticmethod
|
||||
def create_character(id, user, api_id):
|
||||
return EveManager.create_character_obj(adapter.get_character(id), user, api_id)
|
||||
return EveManager.create_character_obj(EveManager.get_character(id), user, api_id)
|
||||
|
||||
@staticmethod
|
||||
def create_character_obj(character, user, api_id):
|
||||
EveCharacter.objects.create(
|
||||
character_id = character.id,
|
||||
character_name = character.name,
|
||||
corporation_id = character.corp.id,
|
||||
corporation_name = character.corp.name,
|
||||
corporation_ticker = character.corp.ticker,
|
||||
alliance_id = character.alliance.id,
|
||||
alliance_name = character.alliance.name,
|
||||
user = user,
|
||||
api_id = api_id,
|
||||
return EveCharacter.objects.create(
|
||||
character_id=character.id,
|
||||
character_name=character.name,
|
||||
corporation_id=character.corp.id,
|
||||
corporation_name=character.corp.name,
|
||||
corporation_ticker=character.corp.ticker,
|
||||
alliance_id=character.alliance.id,
|
||||
alliance_name=character.alliance.name,
|
||||
user=user,
|
||||
api_id=api_id,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_character(id):
|
||||
return EveManager.update_character_obj(adapter.get_character(id))
|
||||
return EveManager.update_character_obj(EveManager.get_character(id))
|
||||
|
||||
@staticmethod
|
||||
def update_character_obj(char):
|
||||
@@ -47,6 +56,7 @@ class EveManager:
|
||||
model.alliance_id = char.alliance.id
|
||||
model.alliance_name = char.alliance.name
|
||||
model.save()
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def create_api_keypair(api_id, api_key, user_id):
|
||||
@@ -61,45 +71,56 @@ class EveManager:
|
||||
else:
|
||||
logger.warn("Attempting to create existing api keypair with id %s" % api_id)
|
||||
|
||||
@classmethod
|
||||
def get_alliance(cls, alliance_id):
|
||||
return cls.get_adapter().get_alliance(alliance_id)
|
||||
|
||||
@staticmethod
|
||||
def create_alliance(id, is_blue=False):
|
||||
return EveManager.create_alliance_obj(adapter.get_alliance(id), is_blue=is_blue)
|
||||
return EveManager.create_alliance_obj(EveManager.get_alliance(id), is_blue=is_blue)
|
||||
|
||||
@staticmethod
|
||||
def create_alliance_obj(alliance, is_blue=False):
|
||||
EveAllianceInfo.objects.create(
|
||||
alliance_id = alliance.id,
|
||||
alliance_name = alliance.name,
|
||||
alliance_ticker = alliance.ticker,
|
||||
executor_corp_id = alliance.executor_corp_id,
|
||||
is_blue = is_blue,
|
||||
return EveAllianceInfo.objects.create(
|
||||
alliance_id=alliance.id,
|
||||
alliance_name=alliance.name,
|
||||
alliance_ticker=alliance.ticker,
|
||||
executor_corp_id=alliance.executor_corp_id,
|
||||
is_blue=is_blue,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_alliance(id, is_blue=None):
|
||||
return EveManager.update_alliance_obj(adapter.get_alliance(id), is_blue=is_blue)
|
||||
return EveManager.update_alliance_obj(EveManager.get_alliance(id), is_blue=is_blue)
|
||||
|
||||
@staticmethod
|
||||
def update_alliance_obj(alliance, is_blue=None):
|
||||
model = EveAllianceInfo.objects.get(alliance_id=alliance.id)
|
||||
model.executor_corp_id = alliance.executor_corp_id
|
||||
model.is_blue = model.is_blue if is_blue == None else is_blue
|
||||
model.is_blue = model.is_blue if is_blue is None else is_blue
|
||||
model.save()
|
||||
return model
|
||||
|
||||
@staticmethod
|
||||
def populate_alliance(id):
|
||||
alliance_model = EveAllianceInfo.objects.get(alliance_id=id)
|
||||
alliance = adapter.get_alliance(id)
|
||||
alliance = EveManager.get_alliance(id)
|
||||
for corp_id in alliance.corp_ids:
|
||||
if not EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
|
||||
EveManager.create_corporation(corp_id, is_blue=alliance_model.is_blue)
|
||||
EveManager.create_corporation(corp_id, is_blue=alliance_model.is_blue)
|
||||
EveCorporationInfo.objects.filter(corporation_id__in=alliance.corp_ids).update(alliance=alliance_model)
|
||||
EveCorporationInfo.objects.filter(alliance=alliance_model).exclude(corporation_id__in=alliance.corp_ids).update(alliance=None)
|
||||
|
||||
EveCorporationInfo.objects.filter(alliance=alliance_model).exclude(corporation_id__in=alliance.corp_ids).update(
|
||||
alliance=None)
|
||||
if alliance_model.is_blue:
|
||||
EveCorporationInfo.objects.filter(alliance=alliance_model).update(is_blue=True)
|
||||
|
||||
@classmethod
|
||||
def get_corporation(cls, corp_id):
|
||||
return cls.get_adapter().get_corp(corp_id)
|
||||
|
||||
@staticmethod
|
||||
def create_corporation(id, is_blue=False):
|
||||
return EveManager.create_corporation_obj(adapter.get_corp(id), is_blue=is_blue)
|
||||
return EveManager.create_corporation_obj(EveManager.get_corporation(id), is_blue=is_blue)
|
||||
|
||||
@staticmethod
|
||||
def create_corporation_obj(corp, is_blue=False):
|
||||
@@ -107,18 +128,18 @@ class EveManager:
|
||||
alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
|
||||
except EveAllianceInfo.DoesNotExist:
|
||||
alliance = None
|
||||
EveCorporationInfo.objects.create(
|
||||
corporation_id = corp.id,
|
||||
corporation_name = corp.name,
|
||||
corporation_ticker = corp.ticker,
|
||||
member_count = corp.members,
|
||||
alliance = alliance,
|
||||
is_blue = is_blue,
|
||||
return EveCorporationInfo.objects.create(
|
||||
corporation_id=corp.id,
|
||||
corporation_name=corp.name,
|
||||
corporation_ticker=corp.ticker,
|
||||
member_count=corp.members,
|
||||
alliance=alliance,
|
||||
is_blue=is_blue,
|
||||
)
|
||||
|
||||
@staticmethod
|
||||
def update_corporation(id, is_blue=None):
|
||||
return EveManager.update_corporation_obj(adapter.get_corp(id), is_blue=is_blue)
|
||||
return EveManager.update_corporation_obj(EveManager.get_corporation(id), is_blue=is_blue)
|
||||
|
||||
@staticmethod
|
||||
def update_corporation_obj(corp, is_blue=None):
|
||||
@@ -128,13 +149,18 @@ class EveManager:
|
||||
model.alliance = EveAllianceInfo.objects.get(alliance_id=corp.alliance_id)
|
||||
except EveAllianceInfo.DoesNotExist:
|
||||
model.alliance = None
|
||||
model.is_blue = model.is_blue if is_blue == None else is_blue
|
||||
model.is_blue = model.is_blue if is_blue is None else is_blue
|
||||
model.save()
|
||||
return model
|
||||
|
||||
@classmethod
|
||||
def get_itemtype(cls, type_id):
|
||||
return cls.get_adapter().get_itemtype(type_id)
|
||||
|
||||
@staticmethod
|
||||
def get_characters_from_api(api):
|
||||
char_result = EveApiManager.get_characters_from_api(api.api_id, api.api_key).result
|
||||
provider = EveXmlProvider(adapter=adapter)
|
||||
provider = EveXmlProvider(adapter=EveManager.get_adapter())
|
||||
return [provider._build_character(result) for id, result in char_result.items()]
|
||||
|
||||
@staticmethod
|
||||
@@ -145,6 +171,14 @@ class EveManager:
|
||||
else:
|
||||
logger.debug("No api keypairs found for user %s" % user)
|
||||
|
||||
@staticmethod
|
||||
def get_all_api_key_pairs():
|
||||
if EveApiKeyPair.objects.exists():
|
||||
logger.debug("Returning all api keypairs.")
|
||||
return EveApiKeyPair.objects.all()
|
||||
else:
|
||||
logger.debug("No api keypairs found.")
|
||||
|
||||
@staticmethod
|
||||
def check_if_api_key_pair_exist(api_id):
|
||||
if EveApiKeyPair.objects.filter(api_id=api_id).exists():
|
||||
@@ -222,6 +256,16 @@ class EveManager:
|
||||
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def get_main_character(user):
|
||||
"""
|
||||
Get a characters main
|
||||
:param user: django.contrib.auth.models.User
|
||||
:return: EveCharacter
|
||||
"""
|
||||
authserviceinfo = AuthServicesInfo.objects.get(user=user)
|
||||
return EveManager.get_character_by_id(authserviceinfo.main_char_id)
|
||||
|
||||
@staticmethod
|
||||
def get_characters_by_api_id(api_id):
|
||||
return EveCharacter.objects.filter(api_id=api_id)
|
||||
|
||||
122
eveonline/migrations/0007_unique_id_name.py
Normal file
@@ -0,0 +1,122 @@
|
||||
# -*- coding: utf-8 -*-
|
||||
# Generated by Django 1.10.5 on 2017-01-18 13:20
|
||||
from __future__ import unicode_literals
|
||||
|
||||
from django.db import migrations, models
|
||||
|
||||
|
||||
def get_duplicates(items):
|
||||
return set([item for item in items if items.count(item) > 1])
|
||||
|
||||
|
||||
def enforce_unique_characters(apps, schema_editor):
|
||||
EveCharacter = apps.get_model('eveonline', 'EveCharacter')
|
||||
|
||||
ids = [c.character_id for c in EveCharacter.objects.all()]
|
||||
duplicates = get_duplicates(ids)
|
||||
for c_id in duplicates:
|
||||
dupes = EveCharacter.objects.filter(character_id=c_id)
|
||||
dupes.exclude(pk=dupes[0].pk).delete()
|
||||
|
||||
names = [c.character_name for c in EveCharacter.objects.all()]
|
||||
duplicates = get_duplicates(names)
|
||||
for name in duplicates:
|
||||
dupes = EveCharacter.objects.filter(character_name=name)
|
||||
dupes.exclude(pk=dupes[0].pk).delete()
|
||||
|
||||
|
||||
def enforce_unique_corporations(apps, schema_editor):
|
||||
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
|
||||
|
||||
ids = [c.corporation_id for c in EveCorporationInfo.objects.all()]
|
||||
duplicates = get_duplicates(ids)
|
||||
for c_id in duplicates:
|
||||
dupes = EveCorporationInfo.objects.filter(corporation_id=c_id)
|
||||
dupes.exclude(pk=dupes[0].pk).delete()
|
||||
|
||||
names = [c.corporation_name for c in EveCorporationInfo.objects.all()]
|
||||
duplicates = get_duplicates(names)
|
||||
for name in duplicates:
|
||||
dupes = EveCorporationInfo.objects.filter(character_name=name)
|
||||
dupes.exclude(pk=dupes[0].pk).delete()
|
||||
|
||||
|
||||
def enforce_unique_alliances(apps, schema_editor):
|
||||
EveAllianceInfo = apps.get_model('eveonline', 'EveAllianceInfo')
|
||||
EveCorporationInfo = apps.get_model('eveonline', 'EveCorporationInfo')
|
||||
|
||||
ids = [a.alliance_id for a in EveAllianceInfo.objects.all()]
|
||||
duplicates = get_duplicates(ids)
|
||||
for a_id in duplicates:
|
||||
dupes = EveAllianceInfo.objects.filter(alliance_id=a_id)
|
||||
to_be_kept = dupes[0]
|
||||
EveCorporationInfo.objects.filter(alliance__pk__in=[a.pk for a in dupes.exclude(pk=to_be_kept.pk)]).update(
|
||||
alliance=to_be_kept.pk)
|
||||
dupes.exclude(pk=to_be_kept.pk).delete()
|
||||
|
||||
names = [a.alliance_name for a in EveAllianceInfo.objects.all()]
|
||||
duplicates = get_duplicates(names)
|
||||
for name in duplicates:
|
||||
dupes = EveAllianceInfo.objects.filter(alliance_name=name)
|
||||
to_be_kept = dupes[0]
|
||||
EveCorporationInfo.objects.filter(alliance__in=[a.pk for a in dupes.exclude(pk=to_be_kept.pk)]).update(
|
||||
alliance=to_be_kept.pk)
|
||||
dupes.exclude(pk=to_be_kept.pk).delete()
|
||||
|
||||
|
||||
def enforce_unique_apis(apps, schema_editor):
|
||||
EveApiKeyPair = apps.get_model('eveonline', 'EveApiKeyPair')
|
||||
|
||||
ids = [api.api_id for api in EveApiKeyPair.objects.all()]
|
||||
duplicates = get_duplicates(ids)
|
||||
for api_id in duplicates:
|
||||
dupes = EveApiKeyPair.objects.filter(api_id=api_id)
|
||||
dupes.exclude(pk=dupes[0].pk).delete()
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
dependencies = [
|
||||
('eveonline', '0006_allow_null_evecharacter_alliance'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RunPython(enforce_unique_characters, migrations.RunPython.noop),
|
||||
migrations.RunPython(enforce_unique_corporations, migrations.RunPython.noop),
|
||||
migrations.RunPython(enforce_unique_alliances, migrations.RunPython.noop),
|
||||
migrations.RunPython(enforce_unique_apis, migrations.RunPython.noop),
|
||||
migrations.AlterField(
|
||||
model_name='evecharacter',
|
||||
name='character_id',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='evecharacter',
|
||||
name='character_name',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='evecorporationinfo',
|
||||
name='corporation_id',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='evecorporationinfo',
|
||||
name='corporation_name',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eveallianceinfo',
|
||||
name='alliance_id',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eveallianceinfo',
|
||||
name='alliance_name',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
migrations.AlterField(
|
||||
model_name='eveapikeypair',
|
||||
name='api_id',
|
||||
field=models.CharField(max_length=254, unique=True),
|
||||
),
|
||||
]
|
||||
@@ -6,8 +6,8 @@ from django.contrib.auth.models import User
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EveCharacter(models.Model):
|
||||
character_id = models.CharField(max_length=254)
|
||||
character_name = models.CharField(max_length=254)
|
||||
character_id = models.CharField(max_length=254, unique=True)
|
||||
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)
|
||||
@@ -22,7 +22,7 @@ class EveCharacter(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EveApiKeyPair(models.Model):
|
||||
api_id = models.CharField(max_length=254)
|
||||
api_id = models.CharField(max_length=254, unique=True)
|
||||
api_key = models.CharField(max_length=254)
|
||||
user = models.ForeignKey(User, blank=True, null=True)
|
||||
sso_verified = models.BooleanField(default=False)
|
||||
@@ -33,8 +33,8 @@ class EveApiKeyPair(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EveAllianceInfo(models.Model):
|
||||
alliance_id = models.CharField(max_length=254)
|
||||
alliance_name = models.CharField(max_length=254)
|
||||
alliance_id = models.CharField(max_length=254, unique=True)
|
||||
alliance_name = models.CharField(max_length=254, unique=True)
|
||||
alliance_ticker = models.CharField(max_length=254)
|
||||
executor_corp_id = models.CharField(max_length=254)
|
||||
is_blue = models.BooleanField(default=False)
|
||||
@@ -45,8 +45,8 @@ class EveAllianceInfo(models.Model):
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EveCorporationInfo(models.Model):
|
||||
corporation_id = models.CharField(max_length=254)
|
||||
corporation_name = models.CharField(max_length=254)
|
||||
corporation_id = models.CharField(max_length=254, unique=True)
|
||||
corporation_name = models.CharField(max_length=254, unique=True)
|
||||
corporation_ticker = models.CharField(max_length=254)
|
||||
member_count = models.IntegerField()
|
||||
is_blue = models.BooleanField(default=False)
|
||||
|
||||
@@ -1,14 +1,23 @@
|
||||
from django.utils.encoding import python_2_unicode_compatible
|
||||
from esi.clients import esi_client_factory
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
import json
|
||||
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
|
||||
import evelink
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
# optional setting to control cached object lifespan
|
||||
OBJ_CACHE_DURATION = int(getattr(settings, 'EVEONLINE_OBJ_CACHE_DURATION', 300))
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class ObjectNotFound(Exception):
|
||||
def __init__(self, id, type):
|
||||
self.id = id
|
||||
self.type = type
|
||||
def __init__(self, obj_id, type_name):
|
||||
self.id = obj_id
|
||||
self.type = type_name
|
||||
|
||||
def __str__(self):
|
||||
return '%s with ID %s not found.' % (self.type, self.id)
|
||||
@@ -32,6 +41,16 @@ class Entity(object):
|
||||
def __eq__(self, other):
|
||||
return self.id == other.id
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data_dict):
|
||||
return cls(data_dict['id'], data_dict['name'])
|
||||
|
||||
|
||||
class Corporation(Entity):
|
||||
def __init__(self, provider, id, name, ticker, ceo_id, members, alliance_id):
|
||||
@@ -58,6 +77,28 @@ class Corporation(Entity):
|
||||
self._ceo = self.provider.get_character(self.ceo_id)
|
||||
return self._ceo
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'ticker': self.ticker,
|
||||
'ceo_id': self.ceo_id,
|
||||
'members': self.members,
|
||||
'alliance_id': self.alliance_id
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict):
|
||||
return cls(
|
||||
None,
|
||||
dict['id'],
|
||||
dict['name'],
|
||||
dict['ticker'],
|
||||
dict['ceo_id'],
|
||||
dict['members'],
|
||||
dict['alliance_id'],
|
||||
)
|
||||
|
||||
|
||||
class Alliance(Entity):
|
||||
def __init__(self, provider, id, name, ticker, corp_ids, executor_corp_id):
|
||||
@@ -70,7 +111,7 @@ class Alliance(Entity):
|
||||
|
||||
def corp(self, id):
|
||||
assert id in self.corp_ids
|
||||
if not id in self._corps:
|
||||
if id not in self._corps:
|
||||
self._corps[id] = self.provider.get_corp(id)
|
||||
self._corps[id]._alliance = self
|
||||
return self._corps[id]
|
||||
@@ -83,6 +124,26 @@ class Alliance(Entity):
|
||||
def executor_corp(self):
|
||||
return self.corp(self.executor_corp_id)
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'ticker': self.ticker,
|
||||
'corp_ids': self.corp_ids,
|
||||
'executor_corp_id': self.executor_corp_id,
|
||||
}
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, dict):
|
||||
return cls(
|
||||
None,
|
||||
dict['id'],
|
||||
dict['name'],
|
||||
dict['ticker'],
|
||||
dict['corp_ids'],
|
||||
dict['executor_corp_id'],
|
||||
)
|
||||
|
||||
|
||||
class Character(Entity):
|
||||
def __init__(self, provider, id, name, corp_id, alliance_id):
|
||||
@@ -96,7 +157,7 @@ class Character(Entity):
|
||||
@property
|
||||
def corp(self):
|
||||
if not self._corp:
|
||||
self._corp = self.provider.get_corp(self.corp_id)
|
||||
self._corp = self.provider.get_corp(self.corp_id)
|
||||
return self._corp
|
||||
|
||||
@property
|
||||
@@ -105,8 +166,40 @@ class Character(Entity):
|
||||
return self.corp.alliance
|
||||
return Entity(None, None)
|
||||
|
||||
def serialize(self):
|
||||
return {
|
||||
'id': self.id,
|
||||
'name': self.name,
|
||||
'corp_id': self.corp_id,
|
||||
'alliance_id': self.alliance_id,
|
||||
}
|
||||
|
||||
class EveProvider:
|
||||
@classmethod
|
||||
def from_dict(cls, dict):
|
||||
return cls(
|
||||
None,
|
||||
dict['id'],
|
||||
dict['name'],
|
||||
dict['corp_id'],
|
||||
dict['alliance_id'],
|
||||
)
|
||||
|
||||
|
||||
class ItemType(Entity):
|
||||
def __init__(self, provider, type_id, name):
|
||||
super(ItemType, self).__init__(type_id, name)
|
||||
self.provider = provider
|
||||
|
||||
@classmethod
|
||||
def from_dict(cls, data_dict):
|
||||
return cls(
|
||||
None,
|
||||
data_dict['id'],
|
||||
data_dict['name'],
|
||||
)
|
||||
|
||||
|
||||
class EveProvider(object):
|
||||
def get_alliance(self, alliance_id):
|
||||
"""
|
||||
:return: an Alliance object for the given ID
|
||||
@@ -119,44 +212,50 @@ class EveProvider:
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_character(self, corp_id):
|
||||
def get_character(self, character_id):
|
||||
"""
|
||||
:return: a Character object for the given ID
|
||||
"""
|
||||
raise NotImplementedError()
|
||||
|
||||
def get_itemtype(self, type_id):
|
||||
"""
|
||||
:return: an ItemType object for the given ID
|
||||
"""
|
||||
raise NotImplemented()
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
class EveSwaggerProvider(EveProvider):
|
||||
def __init__(self, token=None, adapter=None):
|
||||
self.client = esi_client_factory(token=token)
|
||||
self.client = esi_client_factory(token=token, Alliance='v3', Character='v4', Corporation='v2', Universe='v2')
|
||||
self.adapter = adapter or self
|
||||
|
||||
def __str__(self):
|
||||
return 'esi'
|
||||
|
||||
def get_alliance(self, id):
|
||||
def get_alliance(self, alliance_id):
|
||||
try:
|
||||
data = self.client.Alliance.get_alliances_alliance_id(alliance_id=id).result()
|
||||
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=id).result()
|
||||
data = self.client.Alliance.get_alliances_alliance_id(alliance_id=alliance_id).result()
|
||||
corps = self.client.Alliance.get_alliances_alliance_id_corporations(alliance_id=alliance_id).result()
|
||||
model = Alliance(
|
||||
self.adapter,
|
||||
id,
|
||||
alliance_id,
|
||||
data['alliance_name'],
|
||||
data['ticker'],
|
||||
corps,
|
||||
data['executor_corp'],
|
||||
data['executor_corporation_id'],
|
||||
)
|
||||
return model
|
||||
except HTTPNotFound:
|
||||
raise ObjectNotFound(id, 'alliance')
|
||||
raise ObjectNotFound(alliance_id, 'alliance')
|
||||
|
||||
def get_corp(self, id):
|
||||
def get_corp(self, corp_id):
|
||||
try:
|
||||
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=id).result()
|
||||
data = self.client.Corporation.get_corporations_corporation_id(corporation_id=corp_id).result()
|
||||
model = Corporation(
|
||||
self.adapter,
|
||||
id,
|
||||
corp_id,
|
||||
data['corporation_name'],
|
||||
data['ticker'],
|
||||
data['ceo_id'],
|
||||
@@ -165,22 +264,29 @@ class EveSwaggerProvider(EveProvider):
|
||||
)
|
||||
return model
|
||||
except HTTPNotFound:
|
||||
raise ObjectNotFound(id, 'corporation')
|
||||
raise ObjectNotFound(corp_id, 'corporation')
|
||||
|
||||
def get_character(self, id):
|
||||
def get_character(self, character_id):
|
||||
try:
|
||||
data = self.client.Character.get_characters_character_id(character_id=id).result()
|
||||
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(
|
||||
self.adapter,
|
||||
id,
|
||||
character_id,
|
||||
data['name'],
|
||||
data['corporation_id'],
|
||||
alliance_id,
|
||||
)
|
||||
return model
|
||||
except (HTTPNotFound, HTTPUnprocessableEntity):
|
||||
raise ObjectNotFound(id, 'character')
|
||||
raise ObjectNotFound(character_id, 'character')
|
||||
|
||||
def get_itemtype(self, type_id):
|
||||
try:
|
||||
data = self.client.Universe.get_universe_types_type_id(type_id=type_id).result()
|
||||
return ItemType(self.adapter, type_id, data['name'])
|
||||
except (HTTPNotFound, HTTPUnprocessableEntity):
|
||||
raise ObjectNotFound(type_id, 'type')
|
||||
|
||||
|
||||
@python_2_unicode_compatible
|
||||
@@ -224,9 +330,9 @@ class EveXmlProvider(EveProvider):
|
||||
self.adapter,
|
||||
id,
|
||||
corpinfo['name'],
|
||||
corpinfo['ticker'],
|
||||
corpinfo['ceo']['id'],
|
||||
corpinfo['members']['current'],
|
||||
corpinfo['ticker'],
|
||||
corpinfo['alliance']['id'] if corpinfo['alliance'] else None,
|
||||
)
|
||||
return model
|
||||
@@ -250,34 +356,112 @@ class EveXmlProvider(EveProvider):
|
||||
raise e
|
||||
return self._build_character(charinfo)
|
||||
|
||||
def get_itemtype(self, type_id):
|
||||
api = evelink.eve.EVE(api=self.api)
|
||||
try:
|
||||
type_name = api.type_name_from_id(type_id).result
|
||||
assert type_name != 'Unknown Type'
|
||||
return ItemType(self.adapter, type_id, type_name)
|
||||
except AssertionError:
|
||||
raise ObjectNotFound(type_id, 'itemtype')
|
||||
|
||||
|
||||
class EveAdapter(EveProvider):
|
||||
"""
|
||||
Redirects queries to appropriate data source.
|
||||
"""
|
||||
def __init__(self, char_provider, corp_provider, alliance_provider):
|
||||
|
||||
def __init__(self, char_provider, corp_provider, alliance_provider, itemtype_provider):
|
||||
self.char_provider = char_provider
|
||||
self.corp_provider = corp_provider
|
||||
self.alliance_provider = alliance_provider
|
||||
self.itemtype_provider = itemtype_provider
|
||||
self.char_provider.adapter = self
|
||||
self.corp_provider.adapter = self
|
||||
self.alliance_provider.adapter = self
|
||||
self.itemtype_provider.adapter = self
|
||||
|
||||
def __repr__(self):
|
||||
return "<{} (char:{}, corp:{}, alliance:{})>".format(self.__class__.__name__, str(self.char_provider), str(self.corp_provider), str(self.alliance_provider))
|
||||
return "<{} (character:{} corp:{} alliance:{} itemtype:{})>".format(self.__class__.__name__,
|
||||
str(self.char_provider),
|
||||
str(self.corp_provider),
|
||||
str(self.alliance_provider),
|
||||
str(self.itemtype_provider))
|
||||
|
||||
@staticmethod
|
||||
def _get_from_cache(obj_class, id):
|
||||
data = cache.get('%s__%s' % (obj_class.__name__.lower(), id))
|
||||
if data:
|
||||
obj = obj_class.from_dict(json.loads(data))
|
||||
logger.debug('Got from cache: %s' % obj.__repr__())
|
||||
return obj
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _cache(obj):
|
||||
logger.debug('Caching: %s ' % obj.__repr__())
|
||||
cache.set('%s__%s' % (obj.__class__.__name__.lower(), obj.id), json.dumps(obj.serialize()),
|
||||
int(OBJ_CACHE_DURATION))
|
||||
|
||||
def get_character(self, id):
|
||||
return self.char_provider.get_character(id)
|
||||
obj = self._get_from_cache(Character, id)
|
||||
if obj:
|
||||
obj.provider = self
|
||||
else:
|
||||
obj = self._get_character(id)
|
||||
self._cache(obj)
|
||||
return obj
|
||||
|
||||
def get_corp(self, id):
|
||||
return self.corp_provider.get_corp(id)
|
||||
obj = self._get_from_cache(Corporation, id)
|
||||
if obj:
|
||||
obj.provider = self
|
||||
else:
|
||||
obj = self._get_corp(id)
|
||||
self._cache(obj)
|
||||
return obj
|
||||
|
||||
def get_alliance(self, id):
|
||||
obj = self._get_from_cache(Alliance, id)
|
||||
if obj:
|
||||
obj.provider = self
|
||||
else:
|
||||
obj = self._get_alliance(id)
|
||||
self._cache(obj)
|
||||
return obj
|
||||
|
||||
def get_itemtype(self, type_id):
|
||||
obj = self._get_from_cache(ItemType, type_id)
|
||||
if obj:
|
||||
obj.provider = self
|
||||
else:
|
||||
obj = self._get_itemtype(type_id)
|
||||
self._cache(obj)
|
||||
return obj
|
||||
|
||||
def _get_character(self, id):
|
||||
return self.char_provider.get_character(id)
|
||||
|
||||
def _get_corp(self, id):
|
||||
return self.corp_provider.get_corp(id)
|
||||
|
||||
def _get_alliance(self, id):
|
||||
return self.alliance_provider.get_alliance(id)
|
||||
|
||||
def _get_itemtype(self, type_id):
|
||||
return self.itemtype_provider.get_itemtype(type_id)
|
||||
|
||||
def eve_adapter_factory(character_source=settings.EVEONLINE_CHARACTER_PROVIDER, corp_source=settings.EVEONLINE_CORP_PROVIDER, alliance_source=settings.EVEONLINE_ALLIANCE_PROVIDER, api_key=None, token=None):
|
||||
sources = [character_source, corp_source, alliance_source]
|
||||
|
||||
CHARACTER_PROVIDER = getattr(settings, 'EVEONLINE_CHARACTER_PROVIDER', '') or 'esi'
|
||||
CORP_PROVIDER = getattr(settings, 'EVEONLINE_CORP_PROVIDER', '') or 'esi'
|
||||
ALLIANCE_PROVIDER = getattr(settings, 'EVEONLINE_ALLIANCE_PROVIDER', '') or 'esi'
|
||||
ITEMTYPE_PROVIDER = getattr(settings, 'EVEONLINE_ITEMTYPE_PROVIDER', '') or 'esi'
|
||||
|
||||
|
||||
def eve_adapter_factory(character_source=CHARACTER_PROVIDER, corp_source=CORP_PROVIDER,
|
||||
alliance_source=ALLIANCE_PROVIDER, itemtype_source=ITEMTYPE_PROVIDER, api_key=None, token=None):
|
||||
sources = [character_source, corp_source, alliance_source, itemtype_source]
|
||||
providers = []
|
||||
|
||||
if 'xml' in sources:
|
||||
@@ -292,4 +476,4 @@ def eve_adapter_factory(character_source=settings.EVEONLINE_CHARACTER_PROVIDER,
|
||||
providers.append(esi)
|
||||
else:
|
||||
raise ValueError('Unrecognized data source "%s"' % source)
|
||||
return EveAdapter(providers[0], providers[1], providers[2])
|
||||
return EveAdapter(providers[0], providers[1], providers[2], providers[3])
|
||||
|
||||
@@ -1,10 +1,8 @@
|
||||
from __future__ import unicode_literals
|
||||
from django.conf import settings
|
||||
from celery.task import periodic_task
|
||||
from django.contrib.auth.models import User
|
||||
from notifications import notify
|
||||
from celery import task
|
||||
from celery.task.schedules import crontab
|
||||
from authentication.models import AuthServicesInfo
|
||||
from eveonline.managers import EveManager
|
||||
from eveonline.models import EveApiKeyPair
|
||||
@@ -12,15 +10,17 @@ from services.managers.eve_api_manager import EveApiManager
|
||||
from eveonline.models import EveCharacter
|
||||
from eveonline.models import EveCorporationInfo
|
||||
from eveonline.models import EveAllianceInfo
|
||||
from eveonline.providers import eve_adapter_factory
|
||||
from eveonline.providers import eve_adapter_factory, ObjectNotFound
|
||||
from authentication.tasks import set_state
|
||||
import logging
|
||||
import evelink
|
||||
|
||||
from alliance_auth.celeryapp import app
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
@task
|
||||
@app.task
|
||||
def refresh_api(api):
|
||||
logger.debug('Running update on api key %s' % api.api_id)
|
||||
still_valid = True
|
||||
@@ -70,12 +70,13 @@ def refresh_api(api):
|
||||
level="danger")
|
||||
|
||||
|
||||
@task
|
||||
def refresh_user_apis(user):
|
||||
@app.task
|
||||
def refresh_user_apis(pk):
|
||||
user = User.objects.get(pk=pk)
|
||||
logger.debug('Refreshing all APIs belonging to user %s' % user)
|
||||
apis = EveApiKeyPair.objects.filter(user=user)
|
||||
for x in apis:
|
||||
refresh_api(x)
|
||||
refresh_api.apply(args=(x,))
|
||||
# Check our main character
|
||||
auth = AuthServicesInfo.objects.get(user=user)
|
||||
if auth.main_char_id:
|
||||
@@ -91,54 +92,65 @@ def refresh_user_apis(user):
|
||||
set_state(user)
|
||||
|
||||
|
||||
@periodic_task(run_every=crontab(minute=0, hour="*/3"))
|
||||
@app.task
|
||||
def run_api_refresh():
|
||||
if not EveApiManager.check_if_api_server_online():
|
||||
logger.warn("Aborted scheduled API key refresh: API server unreachable")
|
||||
return
|
||||
|
||||
for u in User.objects.all():
|
||||
refresh_user_apis.delay(u)
|
||||
refresh_user_apis.delay(u.pk)
|
||||
|
||||
|
||||
@task
|
||||
def update_corp(id):
|
||||
EveManager.update_corporation(id)
|
||||
@app.task
|
||||
def update_corp(id, is_blue=None):
|
||||
EveManager.update_corporation(id, is_blue=is_blue)
|
||||
|
||||
@task
|
||||
def update_alliance(id):
|
||||
EveManager.update_alliance(id)
|
||||
|
||||
@app.task
|
||||
def update_alliance(id, is_blue=None):
|
||||
EveManager.update_alliance(id, is_blue=is_blue)
|
||||
EveManager.populate_alliance(id)
|
||||
|
||||
|
||||
@periodic_task(run_every=crontab(minute=0, hour="*/2"))
|
||||
@app.task
|
||||
def run_corp_update():
|
||||
if not EveApiManager.check_if_api_server_online():
|
||||
logger.warn("Aborted updating corp and alliance models: API server unreachable")
|
||||
return
|
||||
|
||||
# generate member corps
|
||||
for corp_id in settings.STR_CORP_IDS:
|
||||
if EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
|
||||
update_corp(corp_id)
|
||||
else:
|
||||
EveManager.create_corporation(corp_id)
|
||||
for corp_id in settings.STR_CORP_IDS + settings.STR_BLUE_CORP_IDS:
|
||||
is_blue = True if corp_id in settings.STR_BLUE_CORP_IDS else False
|
||||
try:
|
||||
if EveCorporationInfo.objects.filter(corporation_id=corp_id).exists():
|
||||
update_corp.apply(args=(corp_id,), kwargs={'is_blue': is_blue})
|
||||
else:
|
||||
EveManager.create_corporation(corp_id, is_blue=is_blue)
|
||||
except ObjectNotFound:
|
||||
logger.warn('Bad corp ID in settings: %s' % corp_id)
|
||||
|
||||
# generate member alliances
|
||||
for alliance_id in settings.STR_ALLIANCE_IDS:
|
||||
if EveAllianceInfo.objects.filter(alliance_id=alliance_id).exists():
|
||||
logger.debug("Updating existing owner alliance model with id %s" % alliance_id)
|
||||
update_alliance(alliance_id)
|
||||
else:
|
||||
EveManager.create_alliance(alliance_id)
|
||||
EveManager.populate_alliance(alliance_id)
|
||||
for alliance_id in settings.STR_ALLIANCE_IDS + settings.STR_BLUE_ALLIANCE_IDS:
|
||||
is_blue = True if alliance_id in settings.STR_BLUE_ALLIANCE_IDS else False
|
||||
try:
|
||||
if EveAllianceInfo.objects.filter(alliance_id=alliance_id).exists():
|
||||
logger.debug("Updating existing owner alliance model with id %s" % alliance_id)
|
||||
update_alliance(alliance_id, is_blue=is_blue)
|
||||
else:
|
||||
EveManager.create_alliance(alliance_id, is_blue=is_blue)
|
||||
EveManager.populate_alliance(alliance_id)
|
||||
except ObjectNotFound:
|
||||
logger.warn('Bad alliance ID in settings: %s' % alliance_id)
|
||||
|
||||
# update existing corp models
|
||||
for corp in EveCorporationInfo.objects.all():
|
||||
for corp in EveCorporationInfo.objects.exclude(
|
||||
corporation_id__in=settings.STR_CORP_IDS + settings.STR_BLUE_CORP_IDS):
|
||||
update_corp.delay(corp.corporation_id)
|
||||
|
||||
# update existing alliance models
|
||||
for alliance in EveAllianceInfo.objects.all():
|
||||
for alliance in EveAllianceInfo.objects.exclude(
|
||||
alliance_id__in=settings.STR_ALLIANCE_IDS + settings.STR_BLUE_ALLIANCE_IDS):
|
||||
update_alliance.delay(alliance.alliance_id)
|
||||
|
||||
try:
|
||||
@@ -178,7 +190,7 @@ def run_corp_update():
|
||||
logger.info("Alliance %s no longer meets minimum blue standing threshold" % alliance)
|
||||
alliance.is_blue = False
|
||||
alliance.save()
|
||||
else:
|
||||
elif alliance.alliance_id not in settings.STR_BLUE_ALLIANCE_IDS:
|
||||
logger.info("Alliance %s no longer in standings" % alliance)
|
||||
alliance.is_blue = False
|
||||
alliance.save()
|
||||
@@ -190,7 +202,7 @@ def run_corp_update():
|
||||
logger.info("Corp %s no longer meets minimum blue standing threshold" % corp)
|
||||
corp.is_blue = False
|
||||
corp.save()
|
||||
else:
|
||||
elif corp.corporation_id not in settings.STR_BLUE_CORP_IDS:
|
||||
if corp.alliance:
|
||||
if not corp.alliance.is_blue:
|
||||
logger.info("Corp %s and its alliance %s are no longer blue" % (corp, corp.alliance))
|
||||
@@ -204,22 +216,8 @@ def run_corp_update():
|
||||
logger.error("Model update failed with error code %s" % e.code)
|
||||
|
||||
# delete unnecessary alliance models
|
||||
for alliance in EveAllianceInfo.objects.filter(is_blue=False):
|
||||
logger.debug("Checking to delete alliance %s" % alliance)
|
||||
if not alliance.alliance_id in settings.STR_ALLIANCE_IDS:
|
||||
logger.info("Deleting unnecessary alliance model %s" % alliance)
|
||||
alliance.delete()
|
||||
EveAllianceInfo.objects.filter(is_blue=False).exclude(alliance_id__in=settings.STR_ALLIANCE_IDS).delete()
|
||||
|
||||
# delete unnecessary corp models
|
||||
for corp in EveCorporationInfo.objects.filter(is_blue=False):
|
||||
logger.debug("Checking to delete corp %s" % corp)
|
||||
if not corp.corporation_id in settings.STR_CORP_IDS:
|
||||
logger.debug("Corp %s is not member corp" % corp)
|
||||
if corp.alliance:
|
||||
logger.debug("Corp %s has alliance %s" % (corp, corp.alliance))
|
||||
if not corp.alliance.alliance_id in settings.STR_ALLIANCE_IDS:
|
||||
logger.info("Deleting unnecessary corp model %s" % corp)
|
||||
corp.delete()
|
||||
else:
|
||||
logger.info("Deleting unnecessary corp model %s" % corp)
|
||||
corp.delete()
|
||||
EveCorporationInfo.objects.filter(is_blue=False).exclude(corporation_id__in=settings.STR_CORP_IDS).exclude(
|
||||
alliance__alliance_id__in=settings.STR_ALLIANCE_IDS).delete()
|
||||
|
||||
0
eveonline/templatetags/__init__.py
Normal file
17
eveonline/templatetags/eveonline_extras.py
Normal file
@@ -0,0 +1,17 @@
|
||||
from django import template
|
||||
from django.conf import settings
|
||||
from django.utils.safestring import mark_safe
|
||||
|
||||
register = template.Library()
|
||||
|
||||
|
||||
@register.filter(name='api_link')
|
||||
def api_link(api, style_class):
|
||||
if settings.API_KEY_AUDIT_URL:
|
||||
url = settings.API_KEY_AUDIT_URL.format(api_id=api.api_id, vcode=api.api_key, pk=api.pk)
|
||||
element = "<a href='{url}' class='{style}' target='_new'>{api_id}</a>".format(url=url, style=style_class,
|
||||
api_id=api.api_id)
|
||||
else:
|
||||
element = "<a href='#' class='{style}' onclick='return prompt({prompt}, {vcode})'>{api_id}</a>".format(
|
||||
style=style_class, prompt='"Verification Code"', vcode='"%s"' % api.api_key, api_id=api.api_id)
|
||||
return mark_safe(element)
|
||||
@@ -2,7 +2,7 @@ from __future__ import unicode_literals
|
||||
from django.shortcuts import render, redirect, get_object_or_404
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib import messages
|
||||
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from eveonline.forms import UpdateKeyForm
|
||||
from eveonline.managers import EveManager
|
||||
from authentication.managers import AuthServicesInfoManager
|
||||
@@ -11,7 +11,6 @@ from eveonline.models import EveApiKeyPair, EveCharacter
|
||||
from authentication.models import AuthServicesInfo
|
||||
from authentication.tasks import set_state
|
||||
from eveonline.tasks import refresh_api
|
||||
|
||||
from esi.decorators import token_required
|
||||
from django.conf import settings
|
||||
import logging
|
||||
@@ -30,7 +29,7 @@ def add_api_key(request):
|
||||
api_key=form.cleaned_data['api_key']).exists():
|
||||
# allow orphaned keys to proceed to SSO validation upon re-entry
|
||||
api_key = EveApiKeyPair.objects.get(api_id=form.cleaned_data['api_id'],
|
||||
api_key=form.cleaned_data['api_key'])
|
||||
api_key=form.cleaned_data['api_key'])
|
||||
elif EveApiKeyPair.objects.filter(api_id=form.cleaned_data['api_id']).exists():
|
||||
logger.warn('API %s re-added with different vcode.' % form.cleaned_data['api_id'])
|
||||
EveApiKeyPair.objects.filter(api_id=form.cleaned_data['api_id']).delete()
|
||||
@@ -47,17 +46,18 @@ def add_api_key(request):
|
||||
owner = request.user
|
||||
# Grab characters associated with the key pair
|
||||
characters = EveManager.get_characters_from_api(api_key)
|
||||
[EveManager.create_character_obj(c, owner, api_key.api_id) for c in characters if not EveCharacter.objects.filter(character_id=c.id).exists()]
|
||||
[EveManager.create_character_obj(c, owner, api_key.api_id) for c in characters if
|
||||
not EveCharacter.objects.filter(character_id=c.id).exists()]
|
||||
logger.info("Successfully processed api add form for user %s" % request.user)
|
||||
if not settings.API_SSO_VALIDATION:
|
||||
messages.success(request, 'Added API key %s to your account.' % form.cleaned_data['api_id'])
|
||||
messages.success(request, _('Added API key %(apiid)s to your account.') % {"apiid": form.cleaned_data['api_id']})
|
||||
auth = AuthServicesInfo.objects.get(user=request.user)
|
||||
if not auth.main_char_id:
|
||||
return redirect('auth_characters')
|
||||
return redirect("auth_dashboard")
|
||||
else:
|
||||
logger.debug('Requesting SSO validation of API %s by user %s' % (api_key.api_id, request.user))
|
||||
return render(request, 'registered/apisso.html', context={'api':api_key})
|
||||
return render(request, 'registered/apisso.html', context={'api': api_key})
|
||||
else:
|
||||
logger.debug("Form invalid: returning to form.")
|
||||
else:
|
||||
@@ -74,11 +74,11 @@ def api_sso_validate(request, token, api_id):
|
||||
api = get_object_or_404(EveApiKeyPair, api_id=api_id)
|
||||
if api.user and api.user != request.user:
|
||||
logger.warning('User %s attempting to take ownership of api %s from %s' % (request.user, api_id, api.user))
|
||||
messages.warning(request, 'API %s already claimed by user %s' % (api_id, api.user))
|
||||
messages.warning(request, _('API %(apiid)s already claimed by user %(user)s') % {"apiid": api_id, "user": api.user})
|
||||
return redirect('auth_dashboard')
|
||||
elif api.sso_verified:
|
||||
logger.debug('API %s has already been verified.' % api_id)
|
||||
messages.info(request, 'API %s has already been verified' % api_id)
|
||||
messages.info(request, _('API %(apiid)s has already been verified') % {"apiid": api_id})
|
||||
return redirect('auth_dashboard')
|
||||
logger.debug('API %s has not been verified. Checking if token for %s matches.' % (api_id, token.character_name))
|
||||
characters = EveApiManager.get_characters_from_api(api.api_id, api.api_key).result
|
||||
@@ -87,14 +87,15 @@ def api_sso_validate(request, token, api_id):
|
||||
api.sso_verified = True
|
||||
api.save()
|
||||
EveCharacter.objects.filter(character_id__in=characters).update(user=request.user, api_id=api_id)
|
||||
messages.success(request, 'Confirmed ownership of API %s' % api.api_id)
|
||||
messages.success(request, _('Confirmed ownership of API %(apiid)s') % {"apiid": api.api_id})
|
||||
auth = AuthServicesInfo.objects.get(user=request.user)
|
||||
if not auth.main_char_id:
|
||||
return redirect('auth_characters')
|
||||
return redirect('auth_dashboard')
|
||||
else:
|
||||
messages.warning(request, '%s not found on API %s. Please SSO as a character on the API.' % (token.character_name, api.api_id))
|
||||
return render(request, 'registered/apisso.html', context={'api':api})
|
||||
messages.warning(request, _('%(character)s not found on API %(apiid)s. Please SSO as a character on the API.') % {
|
||||
"character": token.character_name, "apiid": api.api_id})
|
||||
return render(request, 'registered/apisso.html', context={'api': api})
|
||||
|
||||
|
||||
@login_required
|
||||
@@ -110,7 +111,7 @@ def dashboard_view(request):
|
||||
api_chars.append({
|
||||
'id': api.api_id,
|
||||
'sso_verified': api.sso_verified if sso_validation else True,
|
||||
'characters': EveManager.get_characters_by_api_id(api.api_id),
|
||||
'characters': EveCharacter.objects.filter(api_id=api.api_id),
|
||||
})
|
||||
|
||||
context = {
|
||||
@@ -127,10 +128,10 @@ def api_key_removal(request, api_id):
|
||||
authinfo = AuthServicesInfo.objects.get(user=request.user)
|
||||
EveManager.delete_api_key_pair(api_id, request.user.id)
|
||||
EveManager.delete_characters_by_api_id(api_id, request.user.id)
|
||||
messages.success(request, 'Deleted API key %s' % api_id)
|
||||
messages.success(request, _('Deleted API key %(apiid)s') % {"apiid": api_id})
|
||||
logger.info("Succesfully processed api delete request by user %s for api %s" % (request.user, api_id))
|
||||
if not EveCharacter.objects.filter(character_id=authinfo.main_char_id).exists():
|
||||
authinfo.main_char_id = None
|
||||
authinfo.main_char_id = ''
|
||||
authinfo.save()
|
||||
set_state(request.user)
|
||||
return redirect("auth_dashboard")
|
||||
@@ -139,7 +140,7 @@ def api_key_removal(request, api_id):
|
||||
@login_required
|
||||
def characters_view(request):
|
||||
logger.debug("characters_view called by user %s" % request.user)
|
||||
render_items = {'characters': EveManager.get_characters_by_owner_id(request.user.id),
|
||||
render_items = {'characters': EveCharacter.objects.filter(user=request.user),
|
||||
'authinfo': AuthServicesInfo.objects.get(user=request.user)}
|
||||
return render(request, 'registered/characters.html', context=render_items)
|
||||
|
||||
@@ -147,12 +148,13 @@ def characters_view(request):
|
||||
@login_required
|
||||
def main_character_change(request, char_id):
|
||||
logger.debug("main_character_change called by user %s for character id %s" % (request.user, char_id))
|
||||
if EveManager.check_if_character_owned_by_user(char_id, request.user):
|
||||
if EveCharacter.objects.filter(character_id=char_id).exists() and EveCharacter.objects.get(
|
||||
character_id=char_id).user == request.user:
|
||||
AuthServicesInfoManager.update_main_char_id(char_id, request.user)
|
||||
messages.success(request, 'Changed main character ID to %s' % char_id)
|
||||
messages.success(request, _('Changed main character ID to %(charid)s') % {"charid": char_id})
|
||||
set_state(request.user)
|
||||
return redirect("auth_dashboard")
|
||||
messages.error(request, 'Failed to change main character - selected character is not owned by your account.')
|
||||
messages.error(request, _('Failed to change main character - selected character is not owned by your account.'))
|
||||
return redirect("auth_characters")
|
||||
|
||||
|
||||
@@ -162,13 +164,13 @@ def user_refresh_api(request, api_id):
|
||||
if EveApiKeyPair.objects.filter(api_id=api_id).exists():
|
||||
api_key_pair = EveApiKeyPair.objects.get(api_id=api_id)
|
||||
if api_key_pair.user == request.user:
|
||||
refresh_api(api_key_pair)
|
||||
messages.success(request, 'Refreshed API key %s' % api_id)
|
||||
refresh_api.apply(args=(api_key_pair,))
|
||||
messages.success(request, _('Refreshed API key %(apiid)s') % {"apiid": api_id})
|
||||
set_state(request.user)
|
||||
else:
|
||||
messages.warning(request, 'You are not authorized to refresh that API key.')
|
||||
messages.warning(request, _('You are not authorized to refresh that API key.'))
|
||||
logger.warn("User %s not authorized to refresh api id %s" % (request.user, api_id))
|
||||
else:
|
||||
messages.warning(request, 'Unable to locate API key %s' % api_id)
|
||||
messages.warning(request, _('Unable to locate API key %(apiid)s') % {"apiid": api_id})
|
||||
logger.warn("User %s unable to refresh api id %s - api key not found" % (request.user, api_id))
|
||||
return redirect("auth_dashboard")
|
||||
|
||||
@@ -1,8 +1,11 @@
|
||||
{% extends 'public/base.html' %}
|
||||
{% load i18n %}
|
||||
{% block title %}Fleet Participation{% endblock %}
|
||||
{% block page_title %}Fleet Participation{% endblock %}
|
||||
{% block page_title %}{% trans "Fleet Participation" %}{% endblock %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">Character not found!</h1>
|
||||
<h1 class="page-header text-center">{% trans "Character not found!" %}</h1>
|
||||
<div class="col-lg-12 container" id="example">
|
||||
<div class="row">
|
||||
<div class="col-lg-12">
|
||||
@@ -13,8 +16,8 @@
|
||||
<img class="ra-avatar img-responsive" src="https://image.eveonline.com/Character/{{ character_id }}_128.jpg">
|
||||
</div>
|
||||
<div class="col-lg-10 col-sm-2">
|
||||
<div class="alert alert-danger" role="alert">Character not registered!</div>
|
||||
This character is not part of any registered API-key. You must go to <a href=" {% url 'auth_api_key_management' %}">API key management</a> and add an API with the character on before being able to click fleet attendance links.
|
||||
<div class="alert alert-danger" role="alert">{% trans "Character not registered!" %}</div>
|
||||
{% trans "This character is not part of any registered API-key. You must go to" %} <a href=" {% url 'auth_api_key_management' %}">{% trans "API key management</a> and add an API with the character on before being able to click fleet attendance links." %}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -6,8 +6,6 @@
|
||||
{% block title %}Alliance Auth - Fatlink Create{% endblock %}
|
||||
|
||||
{% block page_title %}{% trans "Create Fatlink" %}{% endblock page_title %}
|
||||
{% block extra_css %}
|
||||
<link href="{% static 'css/jquery.datetimepicker.css' %}" rel="stylesheet" type="text/css">{% endblock extra_css %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Edit fatlink "{{ fatlink }}"{% endblocktrans %}
|
||||
<h1 class="page-header text-center">{% trans "Edit fatlink" %} "{{ fatlink }}"
|
||||
<div class="text-right">
|
||||
<form>
|
||||
<button type="submit" onclick="return confirm('Are you sure?')" class="btn btn-danger" name="deletefat" value="True">
|
||||
@@ -37,7 +37,7 @@
|
||||
<td class="text-center">{{ fat.user }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">Docked in {{ fat.system }}</td>
|
||||
<td class="text-center">{% blocktrans %}Docked in {{ fat.system }}{% endblocktrans %}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
@@ -56,8 +56,4 @@
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/js/dateformat.js"></script>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% trans "Participation data statistics for" %} {{ month }}, {{ year }}
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
{% if char_id %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'auth_fatlink_view_user_statistics_month' char_id previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% trans "Previous month" %}</a>
|
||||
@@ -61,8 +61,4 @@
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/js/dateformat.js"></script>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -8,7 +8,7 @@
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% trans "Participation data statistics for" %} {{ year }}
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ year }}{% endblocktrans %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'auth_fatlink_view_personal_statistics_year' previous_year %}" class="btn btn-info">{% trans "Previous year" %}</a>
|
||||
{% if next_year %}
|
||||
@@ -35,8 +35,4 @@
|
||||
</table>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/js/dateformat.js"></script>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -0,0 +1,48 @@
|
||||
{% extends "public/base.html" %}
|
||||
{% load bootstrap %}
|
||||
{% load staticfiles %}
|
||||
{% load i18n %}
|
||||
|
||||
{% block title %}Alliance Auth{% endblock %}
|
||||
{% block page_title %}{% trans "Fatlink Corp Statistics" %}{% endblock page_title %}
|
||||
|
||||
{% block content %}
|
||||
<div class="col-lg-12">
|
||||
<h1 class="page-header text-center">{% blocktrans %}Participation data statistics for {{ month }}, {{ year }}{% endblocktrans %}
|
||||
<div class="text-right">
|
||||
<a href="{% url 'auth_fatlink_view_statistics_corp_month' corpid previous_month|date:"Y" previous_month|date:"m" %}" class="btn btn-info">{% trans "Previous month" %}</a>
|
||||
{% if next_month %}
|
||||
<a href="{% url 'auth_fatlink_view_statistics_corp_month' corpid next_month|date:"Y" next_month|date:"m" %}" class="btn btn-info">{% trans "Next month" %}</a>
|
||||
{% endif %}
|
||||
</div>
|
||||
</h1>
|
||||
{% if fatStats %}
|
||||
<table class="table table-responsive">
|
||||
<tr>
|
||||
<th class="col-md-1"></th>
|
||||
<th class="col-md-2 text-center">{% trans "Main Character" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Characters" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Fats" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Average fats" %}
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
|
||||
</th>
|
||||
</tr>
|
||||
{% for memberStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://image.eveonline.com/Character/{{ memberStat.mainchid }}_32.jpg" class="ra-avatar img-responsive">
|
||||
</td>
|
||||
<td class="text-center">{{ memberStat.mainchar.character_name }}</td>
|
||||
<td class="text-center">{{ memberStat.n_chars }}</td>
|
||||
<td class="text-center">{{ memberStat.n_fats }}</td>
|
||||
<td class="text-center">{{ memberStat.avg_fat }}</td>
|
||||
</tr>
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
{% endblock content %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$("[rel=tooltip]").tooltip();
|
||||
{% endblock extra_script %}
|
||||
@@ -24,14 +24,16 @@
|
||||
<th class="col-md-5 text-center">{% trans "Corp" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Members" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Fats" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Average fats" %}</th>
|
||||
<th class="col-md-2 text-center">{% trans "Average fats" %}
|
||||
<i class="glyphicon glyphicon-question-sign" rel="tooltip" title="Fats ÷ Characters"></i>
|
||||
</th>
|
||||
</tr>
|
||||
{% for corpStat in fatStats %}
|
||||
<tr>
|
||||
<td>
|
||||
<img src="https://image.eveonline.com/Corporation/{{ corpStat.corp.corporation_id }}_32.png" class="ra-avatar img-responsive">
|
||||
</td>
|
||||
<td class="text-center">[{{ corpStat.corp.corporation_ticker }}]</td>
|
||||
<td class="text-center"><a href="{% url 'auth_fatlink_view_statistics_corp' corpStat.corp.corporation_id %}">[{{ corpStat.corp.corporation_ticker }}]</td>
|
||||
<td class="text-center">{{ corpStat.corp.corporation_name }}</td>
|
||||
<td class="text-center">{{ corpStat.corp.member_count }}</td>
|
||||
<td class="text-center">{{ corpStat.n_fats }}</td>
|
||||
@@ -41,8 +43,8 @@
|
||||
</table>
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/js/dateformat.js"></script>
|
||||
|
||||
{% endblock content %}
|
||||
{% block extra_script %}
|
||||
$(document).ready(function(){
|
||||
$("[rel=tooltip]").tooltip();
|
||||
{% endblock extra_script %}
|
||||
|
||||
@@ -36,7 +36,7 @@
|
||||
<td class="text-center">{{ fat.fatlink.name }}</td>
|
||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||
{% if fat.station != "No Station" %}
|
||||
<td class="text-center">Docked in {{ fat.system }}</td>
|
||||
<td class="text-center">{% blocktrans %}Docked in {{ fat.system }}{% endblocktrans %}</td>
|
||||
{% else %}
|
||||
<td class="text-center">{{ fat.system }}</td>
|
||||
{% endif %}
|
||||
@@ -46,7 +46,7 @@
|
||||
{% endfor %}
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">No fleet activity on record.</div>
|
||||
<div class="alert alert-warning text-center">{% trans "No fleet activity on record." %}</div>
|
||||
{% endif %}
|
||||
|
||||
{% if perms.auth.fleetactivitytracking%}
|
||||
@@ -95,12 +95,8 @@
|
||||
|
||||
</table>
|
||||
{% else %}
|
||||
<div class="alert alert-warning text-center">No created fatlinks on record.</div>
|
||||
<div class="alert alert-warning text-center">{% trans "No created fatlinks on record." %}</div>
|
||||
{% endif %}
|
||||
{% endif %}
|
||||
</div>
|
||||
|
||||
|
||||
<script src="/static/js/dateformat.js"></script>
|
||||
|
||||
{% endblock content %}
|
||||
|
||||
@@ -5,12 +5,15 @@ from django.core.exceptions import ObjectDoesNotExist
|
||||
from django.contrib.auth.decorators import login_required
|
||||
from django.contrib.auth.decorators import permission_required
|
||||
from django.core.exceptions import ValidationError
|
||||
from django.utils.translation import ugettext_lazy as _
|
||||
from django.utils import timezone
|
||||
from django.contrib import messages
|
||||
from django.core.paginator import Paginator, EmptyPage, PageNotAnInteger
|
||||
from django.db.models import Q
|
||||
from eveonline.models import EveCharacter
|
||||
from eveonline.models import EveCorporationInfo
|
||||
from eveonline.managers import EveManager
|
||||
from authentication.models import AuthServicesInfo
|
||||
from fleetactivitytracking.forms import FatlinkForm
|
||||
from fleetactivitytracking.models import Fatlink, Fat
|
||||
|
||||
@@ -28,14 +31,15 @@ logger = logging.getLogger(__name__)
|
||||
|
||||
FATS_PER_PAGE = int(getattr(settings, 'FATS_PER_PAGE', 20))
|
||||
|
||||
|
||||
def get_page(model_list, page_num):
|
||||
p = Paginator(model_list, FATS_PER_PAGE)
|
||||
try:
|
||||
fats = p.page(page_num)
|
||||
except PageNotAnInteger:
|
||||
fatss = p.page(1)
|
||||
fats = p.page(1)
|
||||
except EmptyPage:
|
||||
fatss = p.page(p.num_pages)
|
||||
fats = p.page(p.num_pages)
|
||||
return fats
|
||||
|
||||
|
||||
@@ -45,13 +49,32 @@ class CorpStat(object):
|
||||
self.corp = corp
|
||||
else:
|
||||
self.corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
|
||||
self.n_fats = Fat.objects.filter(character__corporation_id=self.corp.corporation_id).filter(fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
|
||||
self.n_fats = Fat.objects.filter(character__corporation_id=self.corp.corporation_id).filter(
|
||||
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
|
||||
self.blue = self.corp.is_blue
|
||||
|
||||
def avg_fat(self):
|
||||
return "%.2f" % (float(self.n_fats) / float(self.corp.member_count))
|
||||
|
||||
|
||||
class MemberStat(object):
|
||||
def __init__(self, member, start_of_month, start_of_next_month, mainchid=None):
|
||||
if mainchid:
|
||||
self.mainchid = mainchid
|
||||
else:
|
||||
self.mainchid = AuthServicesInfo.objects.get(user_id=member['user_id']).main_char_id
|
||||
self.mainchar = EveCharacter.objects.get(character_id=self.mainchid)
|
||||
nchars = 0
|
||||
for alliance_id in settings.STR_ALLIANCE_IDS:
|
||||
nchars += EveCharacter.objects.filter(user_id=member['user_id']).filter(alliance_id=alliance_id).count()
|
||||
self.n_chars = nchars
|
||||
self.n_fats = Fat.objects.filter(user_id=member['user_id']).filter(
|
||||
fatlink__fatdatetime__gte=start_of_month).filter(fatlink__fatdatetime__lte=start_of_next_month).count()
|
||||
|
||||
def avg_fat(self):
|
||||
return "%.2f" % (float(self.n_fats) / float(self.n_chars))
|
||||
|
||||
|
||||
def first_day_of_next_month(year, month):
|
||||
if month == 12:
|
||||
return datetime.datetime(year + 1, 1, 1)
|
||||
@@ -85,6 +108,41 @@ def fatlink_view(request):
|
||||
return render(request, 'fleetactivitytracking/fatlinkview.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.fleetactivitytracking_statistics')
|
||||
def fatlink_statistics_corp_view(request, corpid, year=None, month=None):
|
||||
if year is None:
|
||||
year = datetime.date.today().year
|
||||
if month is None:
|
||||
month = datetime.date.today().month
|
||||
|
||||
year = int(year)
|
||||
month = int(month)
|
||||
start_of_month = datetime.datetime(year, month, 1)
|
||||
start_of_next_month = first_day_of_next_month(year, month)
|
||||
start_of_previous_month = first_day_of_previous_month(year, month)
|
||||
fat_stats = {}
|
||||
corp_members = EveCharacter.objects.filter(corporation_id=corpid).values('user_id').distinct()
|
||||
|
||||
for member in corp_members:
|
||||
try:
|
||||
fat_stats[member['user_id']] = MemberStat(member, start_of_month, start_of_next_month)
|
||||
except ObjectDoesNotExist:
|
||||
continue
|
||||
|
||||
# collect and sort stats
|
||||
stat_list = [fat_stats[x] for x in fat_stats]
|
||||
stat_list.sort(key=lambda stat: stat.mainchar.character_name)
|
||||
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.n_chars), reverse=True)
|
||||
|
||||
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
|
||||
'previous_month': start_of_previous_month, 'corpid': corpid}
|
||||
if datetime.datetime.now() > start_of_next_month:
|
||||
context.update({'next_month': start_of_next_month})
|
||||
|
||||
return render(request, 'fleetactivitytracking/fatlinkstatisticscorpview.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
@permission_required('auth.fleetactivitytracking_statistics')
|
||||
def fatlink_statistics_view(request, year=datetime.date.today().year, month=datetime.date.today().month):
|
||||
@@ -95,41 +153,36 @@ def fatlink_statistics_view(request, year=datetime.date.today().year, month=date
|
||||
start_of_previous_month = first_day_of_previous_month(year, month)
|
||||
|
||||
fat_stats = {}
|
||||
|
||||
|
||||
# get FAT stats for member corps
|
||||
for corp_id in settings.STR_CORP_IDS:
|
||||
fat_stats[corp_id] = CorpStat(corp_id, start_of_month, start_of_next_month)
|
||||
for alliance_id in settings.STR_ALLIANCE_IDS:
|
||||
alliance_corps = EveCorporationInfo.objects.filter(alliance__alliance_id=alliance_id)
|
||||
for corp in alliance_corps:
|
||||
fat_stats[corp.corporation_id] = CorpStat(corp.corporation_id, start_of_month, start_of_next_month)
|
||||
query = Q(corporation_id__in=settings.STR_CORP_IDS) | Q(alliance__alliance_id__in=settings.STR_ALLIANCE_IDS)
|
||||
for corp in EveCorporationInfo.objects.filter(query).distinct():
|
||||
fat_stats[corp.corporation_id] = CorpStat(corp.corporation_id, start_of_month, start_of_next_month)
|
||||
|
||||
# get FAT stats for corps not in alliance
|
||||
fats_in_span = Fat.objects.filter(fatlink__fatdatetime__gte=start_of_month).filter(
|
||||
fatlink__fatdatetime__lt=start_of_next_month).exclude(character__corporation_id__in=fat_stats)
|
||||
|
||||
for fat in fats_in_span:
|
||||
if not fat.character.corporation_id in fat_stats:
|
||||
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month, start_of_next_month)
|
||||
for fat in fats_in_span.exclude(character__corporation_id__in=fat_stats):
|
||||
if EveCorporationInfo.objects.filter(corporation_id=fat.character.corporation_id).exists():
|
||||
fat_stats[fat.character.corporation_id] = CorpStat(fat.character.corporation_id, start_of_month,
|
||||
start_of_next_month)
|
||||
|
||||
# collect and sort stats
|
||||
stat_list = [fat_stats[x] for x in fat_stats]
|
||||
stat_list.sort(key=lambda stat: stat.corp.corporation_name)
|
||||
stat_list.sort(key=lambda stat: (stat.n_fats, stat.n_fats / stat.corp.member_count), reverse=True)
|
||||
|
||||
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
|
||||
'previous_month': start_of_previous_month}
|
||||
if datetime.datetime.now() > start_of_next_month:
|
||||
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
|
||||
'previous_month': start_of_previous_month, 'next_month': start_of_next_month}
|
||||
else:
|
||||
context = {'fatStats': stat_list, 'month': start_of_month.strftime("%B"), 'year': year,
|
||||
'previous_month': start_of_previous_month}
|
||||
context.update({'next_month': start_of_next_month})
|
||||
|
||||
return render(request, 'fleetactivitytracking/fatlinkstatisticsview.html', context=context)
|
||||
|
||||
|
||||
@login_required
|
||||
def fatlink_personal_statistics_view(request, year=datetime.date.today().year, main_name=None):
|
||||
def fatlink_personal_statistics_view(request, year=datetime.date.today().year):
|
||||
year = int(year)
|
||||
logger.debug("Personal statistics view for year %i called by %s" % (year, request.user))
|
||||
|
||||
@@ -191,7 +244,8 @@ def fatlink_monthly_personal_statistics_view(request, year, month, char_id=None)
|
||||
|
||||
|
||||
@login_required
|
||||
@token_required(scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
||||
@token_required(
|
||||
scopes=['esi-location.read_location.v1', 'esi-location.read_ship_type.v1', 'esi-universe.read_structures.v1'])
|
||||
def click_fatlink_view(request, token, hash, fatname):
|
||||
try:
|
||||
fatlink = Fatlink.objects.filter(hash=hash)[0]
|
||||
@@ -202,17 +256,23 @@ def click_fatlink_view(request, token, hash, fatname):
|
||||
|
||||
if character:
|
||||
# get data
|
||||
c = token.get_esi_client()
|
||||
c = token.get_esi_client(Location='v1', Universe='v2')
|
||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
||||
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||
location['solar_system_name'] = c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()['solar_system_name']
|
||||
if location['structure_id']:
|
||||
location['station_name'] = c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()['name']
|
||||
elif location['station_id']:
|
||||
location['station_name'] = c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['station_name']
|
||||
location['solar_system_name'] = \
|
||||
c.Universe.get_universe_systems_system_id(system_id=location['solar_system_id']).result()[
|
||||
'name']
|
||||
if location['station_id']:
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||
elif location['structure_id']:
|
||||
c = token.get_esi_client(Universe='v1')
|
||||
location['station_name'] = \
|
||||
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
||||
'name']
|
||||
else:
|
||||
location['station_name'] = "No Station"
|
||||
ship['ship_type_name'] = c.Universe.get_universe_types_type_id(type_id=ship['ship_type_id']).result()['type_name']
|
||||
ship['ship_type_name'] = EveManager.get_itemtype(ship['ship_type_id']).name
|
||||
|
||||
fat = Fat()
|
||||
fat.system = location['solar_system_name']
|
||||
@@ -224,7 +284,7 @@ def click_fatlink_view(request, token, hash, fatname):
|
||||
try:
|
||||
fat.full_clean()
|
||||
fat.save()
|
||||
messages.success(request, 'Fleet participation registered.')
|
||||
messages.success(request, _('Fleet participation registered.'))
|
||||
except ValidationError as e:
|
||||
err_messages = []
|
||||
for errorname, message in e.message_dict.items():
|
||||
@@ -235,9 +295,10 @@ def click_fatlink_view(request, token, hash, fatname):
|
||||
'character_name': token.character_name}
|
||||
return render(request, 'fleetactivitytracking/characternotexisting.html', context=context)
|
||||
else:
|
||||
messages.error(request, 'FAT link has expired.')
|
||||
messages.error(request, _('FAT link has expired.'))
|
||||
except (ObjectDoesNotExist, KeyError):
|
||||
messages.error(request, 'Invalid FAT link.')
|
||||
logger.exception("Failed to process FAT link.")
|
||||
messages.error(request, _('Invalid FAT link.'))
|
||||
return redirect('auth_fatlink_view')
|
||||
|
||||
|
||||
|
||||
@@ -1 +1,2 @@
|
||||
from __future__ import unicode_literals
|
||||
default_app_config = 'fleetup.apps.FleetupConfig'
|
||||
|
||||
@@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
@@ -1 +0,0 @@
|
||||
from __future__ import unicode_literals
|
||||
190
fleetup/managers.py
Normal file
@@ -0,0 +1,190 @@
|
||||
from __future__ import unicode_literals
|
||||
from django.conf import settings
|
||||
from django.core.cache import cache
|
||||
from django.utils import timezone
|
||||
from datetime import datetime
|
||||
|
||||
import logging
|
||||
import requests
|
||||
import hashlib
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
class FleetUpManager:
|
||||
APP_KEY = settings.FLEETUP_APP_KEY
|
||||
USER_ID = settings.FLEETUP_USER_ID
|
||||
API_ID = settings.FLEETUP_API_ID
|
||||
GROUP_ID = settings.FLEETUP_GROUP_ID
|
||||
BASE_URL = "http://api.fleet-up.com/Api.svc/{}/{}/{}".format(APP_KEY, USER_ID, API_ID)
|
||||
|
||||
TZ = timezone.utc
|
||||
|
||||
def __init__(self):
|
||||
pass
|
||||
|
||||
@classmethod
|
||||
def _request_cache_key(cls, url):
|
||||
h = hashlib.sha1()
|
||||
h.update(url.encode('utf-8'))
|
||||
return 'FLEETUP_ENDPOINT_' + h.hexdigest()
|
||||
|
||||
@classmethod
|
||||
def _cache_until_seconds(cls, cache_until_json):
|
||||
# Format comes in like "/Date(1493896236163)/"
|
||||
try:
|
||||
epoch_ms = int(cache_until_json[6:-2])
|
||||
cache_delta = datetime.fromtimestamp(epoch_ms/1000) - datetime.now()
|
||||
cache_delta_seconds = cache_delta.total_seconds()
|
||||
if cache_delta_seconds < 0:
|
||||
return 0
|
||||
elif cache_delta_seconds > 3600:
|
||||
return 3600
|
||||
else:
|
||||
return cache_delta_seconds
|
||||
except TypeError:
|
||||
logger.debug("Couldn't convert CachedUntil time, defaulting to 600 seconds")
|
||||
return 600
|
||||
|
||||
@classmethod
|
||||
def get_endpoint(cls, url):
|
||||
try:
|
||||
cache_key = cls._request_cache_key(url)
|
||||
cached = cache.get(cache_key)
|
||||
if cached:
|
||||
return cached
|
||||
|
||||
r = requests.get(url)
|
||||
r.raise_for_status()
|
||||
|
||||
json = r.json()
|
||||
|
||||
if json['Success']:
|
||||
cache.set(cache_key, json, cls._cache_until_seconds(json['CachedUntilUTC']))
|
||||
return json
|
||||
except requests.exceptions.ConnectionError:
|
||||
logger.warning("Can't connect to Fleet-Up API, is it offline?!")
|
||||
except requests.HTTPError:
|
||||
logger.exception("Error accessing Fleetup API")
|
||||
return None
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_members(cls):
|
||||
url = "{}/GroupCharacters/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
try:
|
||||
fmembers = cls.get_endpoint(url)
|
||||
if not fmembers:
|
||||
return None
|
||||
return {row["UserId"]: {"user_id": row["UserId"],
|
||||
"char_name": row["EveCharName"],
|
||||
"char_id": row["EveCharId"],
|
||||
"corporation": row["Corporation"]} for row in fmembers["Data"]}
|
||||
except (ValueError, UnicodeDecodeError, TypeError):
|
||||
logger.debug("No fleetup members retrieved.")
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_operations(cls):
|
||||
url = "{}/Operations/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
foperations = cls.get_endpoint(url)
|
||||
if foperations is None:
|
||||
return None
|
||||
return {row["StartString"]: {"subject": row["Subject"],
|
||||
"start": timezone.make_aware(
|
||||
datetime.strptime(row["StartString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||
"end": timezone.make_aware(
|
||||
datetime.strptime(row["EndString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||
"operation_id": row["OperationId"],
|
||||
"location": row["Location"],
|
||||
"location_info": row["LocationInfo"],
|
||||
"details": row["Details"],
|
||||
"url": row["Url"],
|
||||
"doctrine": row["Doctrines"],
|
||||
"organizer": row["Organizer"]} for row in foperations["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_timers(cls):
|
||||
url = "{}/Timers/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
ftimers = cls.get_endpoint(url)
|
||||
if not ftimers:
|
||||
return None
|
||||
return {row["ExpiresString"]: {"solarsystem": row["SolarSystem"],
|
||||
"planet": row["Planet"],
|
||||
"moon": row["Moon"],
|
||||
"owner": row["Owner"],
|
||||
"type": row["Type"],
|
||||
"timer_type": row["TimerType"],
|
||||
"expires": timezone.make_aware(
|
||||
datetime.strptime(row["ExpiresString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||
"notes": row["Notes"]} for row in ftimers["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_doctrines(cls):
|
||||
url = "{}/Doctrines/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
fdoctrines = cls.get_endpoint(url)
|
||||
if not fdoctrines:
|
||||
return None
|
||||
return {"fleetup_doctrines": fdoctrines["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_doctrine(cls, doctrinenumber):
|
||||
url = "{}/DoctrineFittings/{}".format(cls.BASE_URL, doctrinenumber)
|
||||
fdoctrine = cls.get_endpoint(url)
|
||||
if not fdoctrine:
|
||||
return None
|
||||
return {"fitting_doctrine": fdoctrine}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_fittings(cls):
|
||||
url = "{}/Fittings/{}".format(cls.BASE_URL, cls.GROUP_ID)
|
||||
ffittings = cls.get_endpoint(url)
|
||||
if not ffittings:
|
||||
return None
|
||||
return {row["FittingId"]: {"fitting_id": row["FittingId"],
|
||||
"name": row["Name"],
|
||||
"icon_id": row["EveTypeId"],
|
||||
"hull": row["HullType"],
|
||||
"shiptype": row["ShipType"],
|
||||
"estimated": row["EstPrice"],
|
||||
"faction": row["Faction"],
|
||||
"categories": row["Categories"],
|
||||
"last_update":
|
||||
timezone.make_aware(
|
||||
datetime.strptime(row["LastUpdatedString"], "%Y-%m-%d %H:%M:%S"), cls.TZ)}
|
||||
for row in ffittings["Data"]}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_fitting(cls, fittingnumber):
|
||||
url = "{}/Fitting/{}".format(cls.BASE_URL, fittingnumber)
|
||||
try:
|
||||
ffitting = cls.get_endpoint(url)
|
||||
if not ffitting:
|
||||
return None
|
||||
return {"fitting_data": ffitting["Data"]}
|
||||
except KeyError:
|
||||
logger.warning("Failed to retrieve fleetup fitting number %s" % fittingnumber)
|
||||
return {"fitting_data": {}}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_doctrineid(cls, fittingnumber):
|
||||
url = "{}/Fitting/{}".format(cls.BASE_URL, fittingnumber)
|
||||
try:
|
||||
fdoctrineid = cls.get_endpoint(url)
|
||||
if not fdoctrineid:
|
||||
return None
|
||||
return fdoctrineid['Data']['Doctrines'][0]['DoctrineId']
|
||||
except (KeyError, IndexError):
|
||||
logger.debug("Fleetup fitting number %s not in a doctrine." % fittingnumber)
|
||||
return {}
|
||||
|
||||
@classmethod
|
||||
def get_fleetup_fitting_eft(cls, fittingnumber):
|
||||
url = "{}/Fitting/{}/eft".format(cls.BASE_URL, fittingnumber)
|
||||
try:
|
||||
ffittingeft = cls.get_endpoint(url)
|
||||
if not ffittingeft:
|
||||
return None
|
||||
return {"fitting_eft": ffittingeft["Data"]["FittingData"]}
|
||||
except KeyError:
|
||||
logger.warning("Fleetup fitting eft not found for fitting number %s" % fittingnumber)
|
||||
return {"fitting_eft": {}}
|
||||