Discourse (#377)

* Initial work on Discourse integration

* Views for discourse

* Discourse group updates
Correct password display

* Removed password functions
Changed delete to suspend user forever
Added unsuspend check to add_user
This commit is contained in:
Adarnof 2016-04-17 15:17:32 -04:00 committed by Mr McClain
parent 0b20f8f72b
commit e29c1d3295
11 changed files with 481 additions and 2 deletions

View File

@ -249,6 +249,7 @@ BLUE_ALLIANCE_GROUPS = 'True' == os.environ.get('AA_BLUE_ALLIANCE_GROUPS', 'Fals
# 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
@ -259,10 +260,12 @@ 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')
#####################
# Blue service Setup
#####################
@ -272,6 +275,7 @@ ENABLE_AUTH_MARKET = 'True' == os.environ.get('AA_ENABLE_AUTH_MARKET', 'False')
# 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
@ -283,6 +287,7 @@ 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')
@ -406,6 +411,17 @@ DISCORD_SERVER_ID = os.environ.get('AA_DISCORD_SERVER_ID', '')
DISCORD_USER_EMAIL = os.environ.get('AA_DISCORD_USER_EMAIL', '')
DISCORD_USER_PASSWORD = os.environ.get('AA_DISCORD_USER_PASSWORD', '')
######################################
# Discourse Configuration
######################################
# DISCOURSE_URL - Web address of the forums (no trailing slash)
# DISCOURSE_API_USERNAME - API account username
# DISCOURSE_API_KEY - API Key
######################################
DISCOURSE_URL = os.environ.get('AA_DISCOURSE_URL', '')
DISCOURSE_API_USERNAME = os.environ.get('AA_DISCOURSE_API_USERNAME', '')
DISCOURSE_API_KEY = os.environ.get('AA_DISCOURSE_API_KEY', '')
#####################################
# IPS4 Configuration
#####################################

View File

@ -137,6 +137,10 @@ urlpatterns = patterns('',
url(r'^deactivate_discord/$', 'services.views.deactivate_discord', name='auth_deactivate_discord'),
url(r'^reset_discord/$', 'services.views.reset_discord', name='auth_reset_discord'),
# Discourse Service Control
url(r'^activate_discourse/$', 'services.views.activate_discourse', name='auth_activate_discourse'),
url(r'^deactivate_discourse/$', 'services.views.deactivate_discourse', name='auth_deactivate_discourse'),
# IPS4 Service Control
url(r'^activate_ips4/$', 'services.views.activate_ips4',
name='auth_activate_ips4'),

View File

@ -122,6 +122,18 @@ class AuthServicesInfoManager:
else:
logger.error("Failed to update user %s discord info: user does not exist." % user)
@staticmethod
def update_user_discourse_info(username, password, user):
if User.objects.filter(username=user.username).exists():
logger.debug("Updating user %s discourse info: username %s" % (user, username))
authserviceinfo = AuthServicesInfoManager.__get_or_create(user)
authserviceinfo.discourse_username = username
authserviceinfo.discourse_password = password
authserviceinfo.save(update_fields=['discourse_username', 'discourse_password'])
logger.info("Updated user %s discourse info in authservicesinfo model." % user)
else:
logger.error("Failed to update user %s discourse info: user does not exist." % user)
@staticmethod
def update_user_ips4_info(username, password, id, user):
if User.objects.filter(username=user.username).exists():

View File

@ -14,6 +14,8 @@ class AuthServicesInfo(models.Model):
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_username = models.CharField(max_length=254, blank=True, default="")
discourse_password = models.CharField(max_length=254, blank=True, default="")
ips4_username = models.CharField(max_length=254, blank=True, default="")
ips4_password = models.CharField(max_length=254, blank=True, default="")
ips4_id = models.CharField(max_length=254, blank=True, default="")

View File

@ -10,6 +10,7 @@ from .tasks import update_forum_groups
from .tasks import update_ipboard_groups
from .tasks import update_discord_groups
from .tasks import update_teamspeak3_groups
from .tasks import update_discourse_groups
from .tasks import update_smf_groups
from authentication.models import AuthServicesInfo
from services.models import AuthTS
@ -36,6 +37,8 @@ def m2m_changed_user_groups(sender, instance, action, *args, **kwargs):
update_discord_groups.delay(instance.pk)
if auth.mumble_username:
update_mumble_groups.delay(instance.pk)
if auth.discourse_username:
update_discourse_groups.delay(instance.pk)
def trigger_all_ts_update():
for auth in AuthServicesInfo.objects.filter(teamspeak3_uid__isnull=False):
@ -56,3 +59,4 @@ def post_save_authts(sender, instance, *args, **kwargs):
def post_delete_authts(sender, instance, *args, **kwargs):
logger.debug("Received post_delete signal from %s" % instance)
trigger_all_ts_update()

View File

@ -11,6 +11,7 @@ from services.managers.phpbb3_manager import Phpbb3Manager
from services.managers.ipboard_manager import IPBoardManager
from services.managers.teamspeak3_manager import Teamspeak3Manager
from services.managers.discord_manager import DiscordManager, DiscordAPIManager
from services.managers.discourse_manager import DiscourseManager
from services.managers.smf_manager import smfManager
from services.models import AuthTS
from services.models import TSgroup
@ -181,6 +182,26 @@ def update_discord_groups(pk):
raise self.retry(countdown = 60 * 10)
logger.debug("Updated user %s discord groups." % user)
@task
def update_discourse_groups(pk):
user = User.objects.get(pk=pk)
logger.debug("Updating discourse groups for user %s" % user)
authserviceinfo = AuthServicesInfo.objects.get(user=user)
groups = []
for group in user.groups.all():
groups.append(str(group.name))
if len(groups) == 0:
logger.debug("No syncgroups found for user. Adding empty group.")
groups.append('empty')
logger.debug("Updating user %s discord groups to %s" % (user, groups))
try:
DiscourseManager.update_groups(authserviceinfo.discourse_username, groups)
except:
logger.warn("Discourse group sync failed for %s, retrying in 10 mins" % user, exc_info=True)
raise self.retry(countdown = 60 * 10)
logger.debug("Updated user %s discord groups." % user)
def assign_corp_group(auth):
corp_group = None
if auth.main_char_id:

View File

@ -0,0 +1,338 @@
import logging
import requests
import os
import datetime
import json
from django.conf import settings
from django.utils import timezone
from services.models import GroupCache
logger = logging.getLogger(__name__)
# not exhaustive, only the ones we need
ENDPOINTS = {
'groups': {
'list': {
'path': "/admin/groups.json",
'method': requests.get,
'args': {
'required': [],
'optional': [],
},
},
'create': {
'path': "/admin/groups",
'method': requests.post,
'args': {
'required': ['name'],
'optional': ['visible'],
}
},
'add_user': {
'path': "/admin/groups/%s/members.json",
'method': requests.put,
'args': {
'required': ['usernames'],
'optional': [],
},
},
'remove_user': {
'path': "/admin/groups/%s/members.json",
'method': requests.delete,
'args': {
'required': ['username'],
'optional': [],
},
},
'delete': {
'path': "/admin/groups/%s.json",
'method': requests.delete,
'args': {
'required': [],
'optional': [],
},
},
},
'users': {
'create': {
'path': "/users",
'method': requests.post,
'args': {
'required': ['name', 'email', 'password', 'username'],
'optional': ['active'],
},
},
'update': {
'path': "/users/%s.json",
'method': requests.put,
'args': {
'required': ['params'],
'optional': [],
}
},
'get': {
'path': "/users/%s.json",
'method': requests.get,
'args': {
'required': [],
'optional': [],
},
},
'activate': {
'path': "/admin/users/%s/activate",
'method': requests.put,
'args': {
'required': [],
'optional': [],
},
},
'set_email': {
'path': "/users/%s/preferences/email",
'method': requests.put,
'args': {
'required': ['email'],
'optional': [],
},
},
'suspend': {
'path': "/admin/users/%s/suspend",
'method': requests.put,
'args': {
'required': ['duration', 'reason'],
'optional': [],
},
},
'unsuspend': {
'path': "/admin/users/%s/unsuspend",
'method': requests.put,
'args': {
'required': [],
'optional': [],
},
},
},
}
class DiscourseManager:
GROUP_CACHE_MAX_AGE = datetime.timedelta(minutes=30)
REVOKED_EMAIL = 'revoked@' + settings.DOMAIN
SUSPEND_DAYS = 99999
SUSPEND_REASON = "Disabled by auth."
@staticmethod
def __exc(endpoint, *args, **kwargs):
params = {
'api_key': settings.DISCOURSE_API_KEY,
'api_username': settings.DISCOURSE_API_USERNAME,
}
if args:
path = endpoint['path'] % args
else:
path = endpoint['path']
data = {}
for arg in endpoint['args']['required']:
data[arg] = kwargs[arg]
for arg in endpoint['args']['optional']:
if arg in kwargs:
data[arg] = kwargs[arg]
for arg in kwargs:
if not arg in endpoint['args']['required'] and not arg in endpoint['args']['optional']:
logger.warn("Received unrecognized kwarg %s for endpoint %s" % (arg, endpoint))
r = endpoint['method'](settings.DISCOURSE_URL + path, params=params, json=data)
out = r.text
try:
if 'errors' in r.json():
logger.error("Discourse execution failed.\nEndpoint: %s\nErrors: %s" % (endpoint, r.json()['errors']))
r.raise_for_status()
if 'success' in r.json():
if not r.json()['success']:
raise Exception("Execution failed")
out = r.json()
except ValueError:
logger.warn("No json data received for endpoint %s" % endpoint)
r.raise_for_status()
return out
@staticmethod
def __generate_random_pass():
return os.urandom(8).encode('hex')
@staticmethod
def __get_groups():
endpoint = ENDPOINTS['groups']['list']
data = DiscourseManager.__exc(endpoint)
return [g for g in data if not g['automatic']]
@staticmethod
def __update_group_cache():
GroupCache.objects.filter(service="discourse").delete()
cache = GroupCache.objects.create(service="discourse")
cache.groups = json.dumps(DiscourseManager.__get_groups())
cache.save()
return cache
@staticmethod
def __get_group_cache():
if not GroupCache.objects.filter(service="discourse").exists():
DiscourseManager.__update_group_cache()
cache = GroupCache.objects.get(service="discourse")
age = timezone.now() - cache.created
if age > DiscourseManager.GROUP_CACHE_MAX_AGE:
logger.debug("Group cache has expired. Triggering update.")
cache = DiscourseManager.__update_group_cache()
return json.loads(cache.groups)
@staticmethod
def __create_group(name):
endpoint = ENDPOINTS['groups']['create']
DiscourseManager.__exc(endpoint, name=name[:20], visible=True)
DiscourseManager.__update_group_cache()
@staticmethod
def __group_name_to_id(name):
cache = DiscourseManager.__get_group_cache()
for g in cache:
if g['name'] == name[0:20]:
return g['id']
logger.debug("Group %s not found on Discourse. Creating" % name)
DiscourseManager.__create_group(name)
return DiscourseManager.__group_name_to_id(name)
@staticmethod
def __group_id_to_name(id):
cache = DiscourseManager.__get_group_cache()
for g in cache:
if g['id'] == id:
return g['name']
raise KeyError("Group ID %s not found on Discourse" % id)
@staticmethod
def __add_user_to_group(id, username):
endpoint = ENDPOINTS['groups']['add_user']
DiscourseManager.__exc(endpoint, id, usernames=[username])
@staticmethod
def __remove_user_from_group(id, username):
endpoint = ENDPOINTS['groups']['remove_user']
DiscourseManager.__exc(endpoint, id, username=username)
@staticmethod
def __generate_group_dict(names):
dict = {}
for name in names:
dict[name] = DiscourseManager.__group_name_to_id(name)
return dict
@staticmethod
def __get_user_groups(username):
data = DiscourseManager.__get_user(username)
return [g['id'] for g in data['user']['groups'] if not g['automatic']]
@staticmethod
def __user_name_to_id(name):
data = DiscourseManager.__get_user(name)
return data['user']['id']
@staticmethod
def __user_id_to_name(id):
raise NotImplementedError
@staticmethod
def __get_user(username):
endpoint = ENDPOINTS['users']['get']
return DiscourseManager.__exc(endpoint, username)
@staticmethod
def __activate_user(username):
endpoint = ENDPOINTS['users']['activate']
id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, id)
@staticmethod
def __update_user(username, **kwargs):
endpoint = ENDPOINTS['users']['update']
id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, id, params=kwargs)
@staticmethod
def __create_user(username, email, password):
endpoint = ENDPOINTS['users']['create']
DiscourseManager.__exc(endpoint, name=username, username=username, email=email, password=password, active=True)
@staticmethod
def __check_if_user_exists(username):
try:
DiscourseManager.__user_name_to_id(username)
return True
except:
return False
@staticmethod
def __suspend_user(username):
id = DiscourseManager.__user_name_to_id(username)
endpoint = ENDPOINTS['users']['suspend']
return DiscourseManager.__exc(endpoint, id, duration=DiscourseManager.SUSPEND_DAYS, reason=DiscourseManager.SUSPEND_REASON)
@staticmethod
def __unsuspend(username):
id = DiscourseManager.__user_name_to_id(username)
endpoint = ENDPOINTS['users']['unsuspend']
return DiscourseManager.__exc(endpoint, id)
@staticmethod
def __set_email(username, email):
endpoint = ENDPOINTS['users']['set_email']
return DiscourseManager.__exc(endpoint, username, email=email)
@staticmethod
def _sanatize_username(username):
sanatized = username.replace(" ", "_")
sanatized = sanatized.replace("'", "")
return sanatized
@staticmethod
def add_user(username, email):
logger.debug("Adding new discourse user %s" % username)
password = DiscourseManager.__generate_random_pass()
safe_username = DiscourseManager._sanatize_username(username)
try:
if DiscourseManager.__check_if_user_exists(safe_username):
logger.debug("Discourse user %s already exists. Reactivating" % safe_username)
DiscourseManager.__unsuspend(safe_username)
else:
logger.debug("Creating new user account for %s" % username)
DiscourseManager.__create_user(safe_username, email, password)
logger.info("Added new discourse user %s" % username)
return safe_username, password
except:
logger.exception("Failed to add new discourse user %s" % username)
return "",""
@staticmethod
def delete_user(username):
logger.debug("Deleting discourse user %s" % username)
try:
DiscourseManager.__suspend_user(username)
logger.info("Deleted discourse user %s" % username)
return True
except:
logger.exception("Failed to delete discourse user %s" % username)
return False
@staticmethod
def update_groups(username, raw_groups):
groups = []
for g in raw_groups:
groups.append(g[:20])
logger.debug("Updating discourse user %s groups to %s" % (username, groups))
group_dict = DiscourseManager.__generate_group_dict(groups)
inv_group_dict = {v:k for k,v in group_dict.items()}
user_groups = DiscourseManager.__get_user_groups(username)
add_groups = [group_dict[x] for x in group_dict if not group_dict[x] in user_groups]
rem_groups = [x for x in user_groups if not inv_group_dict[x] in groups]
if add_groups or rem_groups:
logger.info("Updating discourse user %s groups: adding %s, removing %s" % (username, add_groups, rem_groups))
for g in add_groups:
DiscourseManager.__add_user_to_group(g, username)
for g in rem_groups:
DiscourseManager.__remove_user_from_group(g, username)

View File

@ -46,3 +46,12 @@ class MumbleUser(models.Model):
def __str__(self):
return self.username
class GroupCache(models.Model):
SERVICE_CHOICES = (
("discourse", "discourse"),
)
created = models.DateTimeField(auto_now_add=True)
groups = models.TextField(blank=True, null=True)
service = models.CharField(max_length=254, choices=SERVICE_CHOICES, unique=True)

View File

@ -14,6 +14,7 @@ from managers.mumble_manager import MumbleManager
from managers.ipboard_manager import IPBoardManager
from managers.teamspeak3_manager import Teamspeak3Manager
from managers.discord_manager import DiscordManager
from managers.discourse_manager import DiscourseManager
from managers.ips4_manager import Ips4Manager
from managers.smf_manager import smfManager
from managers.market_manager import marketManager
@ -26,6 +27,7 @@ from celerytask.tasks import update_ipboard_groups
from celerytask.tasks import update_smf_groups
from celerytask.tasks import update_teamspeak3_groups
from celerytask.tasks import update_discord_groups
from celerytask.tasks import update_discourse_groups
from forms import JabberBroadcastForm
from forms import FleetFormatterForm
from forms import DiscordForm
@ -608,6 +610,37 @@ def set_ipboard_password(request):
context = {'form': form, 'service': 'IPBoard', 'error': error}
return render_to_response('registered/service_password.html', context, context_instance=RequestContext(request))
@login_required
@user_passes_test(service_blue_alliance_test)
def activate_discourse(request):
logger.debug("activate_discourse called by user %s" % request.user)
authinfo = AuthServicesInfoManager.get_auth_service_info(request.user)
character = EveManager.get_character_by_id(authinfo.main_char_id)
logger.debug("Adding discourse user for user %s with main character %s" % (request.user, character))
result = DiscourseManager.add_user(character.character_name, request.user.email)
if result[0] != "":
AuthServicesInfoManager.update_user_discourse_info(result[0], result[1], request.user)
logger.debug("Updated authserviceinfo for user %s with discourse credentials. Updating groups." % request.user)
update_discourse_groups.delay(request.user.pk)
logger.info("Successfully activated discourse for user %s" % request.user)
return HttpResponseRedirect("/services/")
logger.error("Unsuccessful attempt to activate forum for user %s" % request.user)
return HttpResponseRedirect("/dashboard")
@login_required
@user_passes_test(service_blue_alliance_test)
def deactivate_discourse(request):
logger.debug("deactivate_discourse called by user %s" % request.user)
authinfo = AuthServicesInfoManager.get_auth_service_info(request.user)
result = DiscourseManager.delete_user(authinfo.discourse_username)
if result:
AuthServicesInfoManager.update_user_discourse_info("", "", request.user)
logger.info("Successfully deactivated discourse for user %s" % request.user)
return HttpResponseRedirect("/services/")
logger.error("Unsuccessful attempt to activate discourse for user %s" % request.user)
return HttpResponseRedirect("/dashboard")
@login_required
@user_passes_test(service_blue_alliance_test)
def activate_ips4(request):
@ -721,7 +754,6 @@ def deactivate_smf(request):
logger.error("Unsuccesful attempt to activate smf for user %s" % request.user)
return HttpResponseRedirect("/dashboard")
@login_required
@user_passes_test(service_blue_alliance_test)
def reset_smf_password(request):

View File

@ -236,6 +236,25 @@
</td>
</tr>
{% endif %}
{% if ENABLE_BLUE_DISCOURSE %}
<td class="text-center">Discourse</td>
<td class="text-center">{{ authinfo.discourse_username }}</td>
<td class="text-center">{{ authinfo.discourse_password }}</td>
<td class="text-center"><a href="{{ DISCOURSE_URL }}">{{ DISCOURSE_URL }}</a></td>
<td class="text-center">
{% ifequal authinfo.discourse_username "" %}
<a href="{% url 'auth_activate_discourse' %}">
<button type="button" class="btn btn-warning"><span
class="glyphicon glyphicon-ok"></span></button>
</a>
{% else %}
<a href="{% url 'auth_deactivate_discourse' %}">
<button type="button" class="btn btn-danger"><span
class="glyphicon glyphicon-remove"></span></button>
</a>
{% endifequal %}
</td>
{% endif %}
{% if ENABLE_BLUE_TEAMSPEAK3 %}
<tr>
<th class="text-center">Service</th>
@ -507,6 +526,25 @@
</td>
</tr>
{% endif %}
{% if ENABLE_AUTH_DISCOURSE %}
<td class="text-center">Discourse</td>
<td class="text-center">{{ authinfo.discourse_username }}</td>
<td class="text-center">{{ authinfo.discourse_password }}</td>
<td class="text-center"><a href="{{ DISCOURSE_URL }}">{{ DISCOURSE_URL }}</a></td>
<td class="text-center">
{% ifequal authinfo.discourse_username "" %}
<a href="{% url 'auth_activate_discourse' %}">
<button type="button" class="btn btn-warning"><span
class="glyphicon glyphicon-ok"></span></button>
</a>
{% else %}
<a href="{% url 'auth_deactivate_discourse' %}">
<button type="button" class="btn btn-danger"><span
class="glyphicon glyphicon-remove"></span></button>
</a>
{% endifequal %}
</td>
{% endif %}
{% if ENABLE_AUTH_TEAMSPEAK3 %}
<tr>
<th class="text-center">Service</th>

View File

@ -37,6 +37,7 @@ def domain_url(request):
'ENABLE_AUTH_IPBOARD': settings.ENABLE_AUTH_IPBOARD,
'ENABLE_AUTH_TEAMSPEAK3': settings.ENABLE_AUTH_TEAMSPEAK3,
'ENABLE_AUTH_DISCORD': settings.ENABLE_AUTH_DISCORD,
'ENABLE_AUTH_DISCOURSE': settings.ENABLE_AUTH_DISCOURSE,
'ENABLE_AUTH_IPS4': settings.ENABLE_AUTH_IPS4,
'ENABLE_AUTH_SMF': settings.ENABLE_AUTH_SMF,
'ENABLE_AUTH_MARKET': settings.ENABLE_AUTH_MARKET,
@ -46,6 +47,7 @@ def domain_url(request):
'ENABLE_BLUE_IPBOARD': settings.ENABLE_BLUE_IPBOARD,
'ENABLE_BLUE_TEAMSPEAK3': settings.ENABLE_BLUE_TEAMSPEAK3,
'ENABLE_BLUE_DISCORD': settings.ENABLE_BLUE_DISCORD,
'ENABLE_BLUE_DISCOURSE': settings.ENABLE_BLUE_DISCOURSE,
'ENABLE_BLUE_IPS4': settings.ENABLE_BLUE_IPS4,
'ENABLE_BLUE_SMF': settings.ENABLE_BLUE_SMF,
'ENABLE_BLUE_MARKET': settings.ENABLE_BLUE_MARKET,
@ -53,6 +55,7 @@ def domain_url(request):
'JACK_KNIFE_URL': settings.JACK_KNIFE_URL,
'DISCORD_SERVER_ID': settings.DISCORD_SERVER_ID,
'KILLBOARD_URL': settings.KILLBOARD_URL,
'DISCOURSE_URL': settings.DISCOURSE_URL,
'IPS4_URL': settings.IPS4_URL,
'SMF_URL': settings.SMF_URL,
'MARKET_URL': settings.MARKET_URL,