mirror of
https://gitlab.com/allianceauth/allianceauth.git
synced 2025-07-09 12:30:15 +02:00
Merge branch 'master' of https://github.com/Adarnof/allianceauth into sso_registration
# Conflicts: # alliance_auth/__init__.py # corputils/models.py # corputils/views.py # eveonline/tasks.py # fleetactivitytracking/views.py # hrapplications/admin.py # requirements.txt
This commit is contained in:
commit
02f2968ee5
@ -1,3 +1,4 @@
|
|||||||
|
# -*- coding: UTF-8 -*-
|
||||||
"""
|
"""
|
||||||
Django settings for alliance_auth project.
|
Django settings for alliance_auth project.
|
||||||
|
|
||||||
@ -195,10 +196,10 @@ MESSAGE_TAGS = {
|
|||||||
|
|
||||||
CACHES = {
|
CACHES = {
|
||||||
"default": {
|
"default": {
|
||||||
"BACKEND": "django_redis.cache.RedisCache",
|
"BACKEND": "redis_cache.RedisCache",
|
||||||
"LOCATION": "redis://127.0.0.1:6379/1",
|
"LOCATION": "localhost:6379",
|
||||||
"OPTIONS": {
|
"OPTIONS": {
|
||||||
"CLIENT_CLASS": "django_redis.client.DefaultClient",
|
"DB": 1,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -9,6 +9,10 @@ from authentication.models import CharacterOwnership, UserProfile
|
|||||||
from bravado.exception import HTTPForbidden
|
from bravado.exception import HTTPForbidden
|
||||||
from corputils.managers import CorpStatsManager
|
from corputils.managers import CorpStatsManager
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -35,7 +39,7 @@ class CorpStats(models.Model):
|
|||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
try:
|
try:
|
||||||
c = self.token.get_esi_client(Character='v4', Corporation='v2')
|
c = self.token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||||
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
assert c.Character.get_characters_character_id(character_id=self.token.character_id).result()[
|
||||||
'corporation_id'] == int(self.corp.corporation_id)
|
'corporation_id'] == int(self.corp.corporation_id)
|
||||||
members = c.Corporation.get_corporations_corporation_id_members(
|
members = c.Corporation.get_corporations_corporation_id_members(
|
||||||
@ -46,7 +50,6 @@ class CorpStats(models.Model):
|
|||||||
# the swagger spec doesn't have a maxItems count
|
# the swagger spec doesn't have a maxItems count
|
||||||
# manual testing says we can do over 350, but let's not risk it
|
# 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_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_name_chunks = [c.Character.get_characters_names(character_ids=id_chunk).result() for id_chunk in
|
||||||
member_id_chunks]
|
member_id_chunks]
|
||||||
member_list = {}
|
member_list = {}
|
||||||
|
1
corputils/swagger.json
Normal file
1
corputils/swagger.json
Normal file
File diff suppressed because one or more lines are too long
@ -10,6 +10,9 @@ from eveonline.managers import EveManager
|
|||||||
from corputils.models import CorpStats
|
from corputils.models import CorpStats
|
||||||
from esi.decorators import token_required
|
from esi.decorators import token_required
|
||||||
from bravado.exception import HTTPError
|
from bravado.exception import HTTPError
|
||||||
|
import os
|
||||||
|
|
||||||
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
|
||||||
|
|
||||||
def access_corpstats_test(user):
|
def access_corpstats_test(user):
|
||||||
@ -27,7 +30,7 @@ def corpstats_add(request, token):
|
|||||||
corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
|
corp_id = EveCharacter.objects.get(character_id=token.character_id).corporation_id
|
||||||
else:
|
else:
|
||||||
corp_id = \
|
corp_id = \
|
||||||
token.get_esi_client(Character='v4').Character.get_characters_character_id(
|
token.get_esi_client(spec_file=SWAGGER_SPEC_PATH).Character.get_characters_character_id(
|
||||||
character_id=token.character_id).result()['corporation_id']
|
character_id=token.character_id).result()['corporation_id']
|
||||||
try:
|
try:
|
||||||
corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
|
corp = EveCorporationInfo.objects.get(corporation_id=corp_id)
|
||||||
|
@ -22,7 +22,7 @@ CentOS:
|
|||||||
|
|
||||||
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`.
|
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
|
sudo cp thirdparty/Supervisor/* /etc/supervisor/conf.d
|
||||||
|
|
||||||
@ -44,6 +44,24 @@ Processes will be `STARTING`, `RUNNING`, or `ERROR`. If an error has occurred, c
|
|||||||
- celery workers: `log/worker.log`
|
- celery workers: `log/worker.log`
|
||||||
- celery beat: `log/beat.log`
|
- celery beat: `log/beat.log`
|
||||||
- authenticator: `log/authenticator.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
|
## Customizing Config Files
|
||||||
|
|
||||||
|
@ -6,6 +6,9 @@ import json
|
|||||||
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
|
from bravado.exception import HTTPNotFound, HTTPUnprocessableEntity
|
||||||
import evelink
|
import evelink
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -228,7 +231,7 @@ class EveProvider(object):
|
|||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class EveSwaggerProvider(EveProvider):
|
class EveSwaggerProvider(EveProvider):
|
||||||
def __init__(self, token=None, adapter=None):
|
def __init__(self, token=None, adapter=None):
|
||||||
self.client = esi_client_factory(token=token, Alliance='v1', Character='v4', Corporation='v2', Universe='v2')
|
self.client = esi_client_factory(token=token, spec_file=SWAGGER_SPEC_PATH)
|
||||||
self.adapter = adapter or self
|
self.adapter = adapter or self
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
@ -244,7 +247,7 @@ class EveSwaggerProvider(EveProvider):
|
|||||||
data['alliance_name'],
|
data['alliance_name'],
|
||||||
data['ticker'],
|
data['ticker'],
|
||||||
corps,
|
corps,
|
||||||
data['executor_corporation_id'],
|
data['executor_corp'],
|
||||||
)
|
)
|
||||||
return model
|
return model
|
||||||
except HTTPNotFound:
|
except HTTPNotFound:
|
||||||
|
1
eveonline/swagger.json
Normal file
1
eveonline/swagger.json
Normal file
File diff suppressed because one or more lines are too long
@ -1 +0,0 @@
|
|||||||
# Create your tests here.
|
|
1
fleetactivitytracking/swagger.json
Normal file
1
fleetactivitytracking/swagger.json
Normal file
File diff suppressed because one or more lines are too long
@ -2,6 +2,8 @@
|
|||||||
{% load i18n %}
|
{% load i18n %}
|
||||||
{% block title %}Fleet Participation{% endblock %}
|
{% block title %}Fleet Participation{% endblock %}
|
||||||
{% block page_title %}{% trans "Fleet Participation" %}{% endblock %}
|
{% block page_title %}{% trans "Fleet Participation" %}{% endblock %}
|
||||||
|
|
||||||
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h1 class="page-header text-center">{% trans "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="col-lg-12 container" id="example">
|
||||||
|
@ -37,7 +37,7 @@
|
|||||||
<td class="text-center">{{ fat.user }}</td>
|
<td class="text-center">{{ fat.user }}</td>
|
||||||
<td class="text-center">{{ fat.character.character_name }}</td>
|
<td class="text-center">{{ fat.character.character_name }}</td>
|
||||||
{% if fat.station != "No Station" %}
|
{% if fat.station != "No Station" %}
|
||||||
<td class="text-center">{% blocktrans %}Docked in {{ fat.system }}{% endblocktrans %}</td>
|
<td class="text-center">{% blocktrans %}Docked in {% endblocktrans %}{{ fat.system }}</td>
|
||||||
{% else %}
|
{% else %}
|
||||||
<td class="text-center">{{ fat.system }}</td>
|
<td class="text-center">{{ fat.system }}</td>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
@ -17,14 +17,14 @@ from fleetactivitytracking.models import Fatlink, Fat
|
|||||||
from authentication.models import CharacterOwnership
|
from authentication.models import CharacterOwnership
|
||||||
from django.contrib.auth.models import User
|
from django.contrib.auth.models import User
|
||||||
from esi.decorators import token_required
|
from esi.decorators import token_required
|
||||||
|
|
||||||
from slugify import slugify
|
from slugify import slugify
|
||||||
|
|
||||||
import string
|
import string
|
||||||
import random
|
import random
|
||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
|
import os
|
||||||
|
|
||||||
|
SWAGGER_SPEC_PATH = os.path.join(os.path.dirname(os.path.abspath(__file__)), 'swagger.json')
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -251,7 +251,7 @@ def click_fatlink_view(request, token, hash, fatname):
|
|||||||
|
|
||||||
if character:
|
if character:
|
||||||
# get data
|
# get data
|
||||||
c = token.get_esi_client(Location='v1', Universe='v2')
|
c = token.get_esi_client(spec_file=SWAGGER_SPEC_PATH)
|
||||||
location = c.Location.get_characters_character_id_location(character_id=token.character_id).result()
|
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()
|
ship = c.Location.get_characters_character_id_ship(character_id=token.character_id).result()
|
||||||
location['solar_system_name'] = \
|
location['solar_system_name'] = \
|
||||||
@ -261,7 +261,6 @@ def click_fatlink_view(request, token, hash, fatname):
|
|||||||
location['station_name'] = \
|
location['station_name'] = \
|
||||||
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
c.Universe.get_universe_stations_station_id(station_id=location['station_id']).result()['name']
|
||||||
elif location['structure_id']:
|
elif location['structure_id']:
|
||||||
c = token.get_esi_client(Universe='v1')
|
|
||||||
location['station_name'] = \
|
location['station_name'] = \
|
||||||
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
c.Universe.get_universe_structures_structure_id(structure_id=location['structure_id']).result()[
|
||||||
'name']
|
'name']
|
||||||
|
@ -1,6 +1,7 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from django.utils import timezone
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
import logging
|
import logging
|
||||||
@ -17,6 +18,8 @@ class FleetUpManager:
|
|||||||
GROUP_ID = settings.FLEETUP_GROUP_ID
|
GROUP_ID = settings.FLEETUP_GROUP_ID
|
||||||
BASE_URL = "http://api.fleet-up.com/Api.svc/{}/{}/{}".format(APP_KEY, USER_ID, API_ID)
|
BASE_URL = "http://api.fleet-up.com/Api.svc/{}/{}/{}".format(APP_KEY, USER_ID, API_ID)
|
||||||
|
|
||||||
|
TZ = timezone.utc
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@ -60,7 +63,7 @@ class FleetUpManager:
|
|||||||
cache.set(cache_key, json, cls._cache_until_seconds(json['CachedUntilUTC']))
|
cache.set(cache_key, json, cls._cache_until_seconds(json['CachedUntilUTC']))
|
||||||
return json
|
return json
|
||||||
except requests.exceptions.ConnectionError:
|
except requests.exceptions.ConnectionError:
|
||||||
logger.warn("Can't connect to Fleet-Up API, is it offline?!")
|
logger.warning("Can't connect to Fleet-Up API, is it offline?!")
|
||||||
except requests.HTTPError:
|
except requests.HTTPError:
|
||||||
logger.exception("Error accessing Fleetup API")
|
logger.exception("Error accessing Fleetup API")
|
||||||
return None
|
return None
|
||||||
@ -87,8 +90,10 @@ class FleetUpManager:
|
|||||||
if foperations is None:
|
if foperations is None:
|
||||||
return None
|
return None
|
||||||
return {row["StartString"]: {"subject": row["Subject"],
|
return {row["StartString"]: {"subject": row["Subject"],
|
||||||
"start": datetime.strptime(row["StartString"], "%Y-%m-%d %H:%M:%S"),
|
"start": timezone.make_aware(
|
||||||
"end": datetime.strptime(row["EndString"], "%Y-%m-%d %H:%M:%S"),
|
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"],
|
"operation_id": row["OperationId"],
|
||||||
"location": row["Location"],
|
"location": row["Location"],
|
||||||
"location_info": row["LocationInfo"],
|
"location_info": row["LocationInfo"],
|
||||||
@ -109,9 +114,9 @@ class FleetUpManager:
|
|||||||
"owner": row["Owner"],
|
"owner": row["Owner"],
|
||||||
"type": row["Type"],
|
"type": row["Type"],
|
||||||
"timer_type": row["TimerType"],
|
"timer_type": row["TimerType"],
|
||||||
"expires": (datetime.strptime(row["ExpiresString"], "%Y-%m-%d %H:%M:%S")),
|
"expires": timezone.make_aware(
|
||||||
|
datetime.strptime(row["ExpiresString"], "%Y-%m-%d %H:%M:%S"), cls.TZ),
|
||||||
"notes": row["Notes"]} for row in ftimers["Data"]}
|
"notes": row["Notes"]} for row in ftimers["Data"]}
|
||||||
return {}
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fleetup_doctrines(cls):
|
def get_fleetup_doctrines(cls):
|
||||||
@ -143,9 +148,10 @@ class FleetUpManager:
|
|||||||
"estimated": row["EstPrice"],
|
"estimated": row["EstPrice"],
|
||||||
"faction": row["Faction"],
|
"faction": row["Faction"],
|
||||||
"categories": row["Categories"],
|
"categories": row["Categories"],
|
||||||
"last_update": (
|
"last_update":
|
||||||
datetime.strptime(row["LastUpdatedString"], "%Y-%m-%d %H:%M:%S"))} for row in
|
timezone.make_aware(
|
||||||
ffittings["Data"]}
|
datetime.strptime(row["LastUpdatedString"], "%Y-%m-%d %H:%M:%S"), cls.TZ)}
|
||||||
|
for row in ffittings["Data"]}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def get_fleetup_fitting(cls, fittingnumber):
|
def get_fleetup_fitting(cls, fittingnumber):
|
||||||
@ -156,7 +162,7 @@ class FleetUpManager:
|
|||||||
return None
|
return None
|
||||||
return {"fitting_data": ffitting["Data"]}
|
return {"fitting_data": ffitting["Data"]}
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.warn("Failed to retrieve fleetup fitting number %s" % fittingnumber)
|
logger.warning("Failed to retrieve fleetup fitting number %s" % fittingnumber)
|
||||||
return {"fitting_data": {}}
|
return {"fitting_data": {}}
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
@ -180,5 +186,5 @@ class FleetUpManager:
|
|||||||
return None
|
return None
|
||||||
return {"fitting_eft": ffittingeft["Data"]["FittingData"]}
|
return {"fitting_eft": ffittingeft["Data"]["FittingData"]}
|
||||||
except KeyError:
|
except KeyError:
|
||||||
logger.warn("Fleetup fitting eft not found for fitting number %s" % fittingnumber)
|
logger.warning("Fleetup fitting eft not found for fitting number %s" % fittingnumber)
|
||||||
return {"fitting_eft": {}}
|
return {"fitting_eft": {}}
|
||||||
|
@ -12,6 +12,7 @@ import json
|
|||||||
import datetime
|
import datetime
|
||||||
|
|
||||||
from django.test import TestCase
|
from django.test import TestCase
|
||||||
|
from django.utils.timezone import make_aware, utc
|
||||||
|
|
||||||
from fleetup.managers import FleetUpManager
|
from fleetup.managers import FleetUpManager
|
||||||
|
|
||||||
@ -148,8 +149,8 @@ class FleetupManagerTestCase(TestCase):
|
|||||||
expected_result = {
|
expected_result = {
|
||||||
'2017-05-06 11:11:11': {
|
'2017-05-06 11:11:11': {
|
||||||
'subject': 'test_operation',
|
'subject': 'test_operation',
|
||||||
'start': datetime.datetime(2017, 5, 6, 11, 11, 11),
|
'start': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc),
|
||||||
'end': datetime.datetime(2017, 5, 6, 12, 12, 12),
|
'end': make_aware(datetime.datetime(2017, 5, 6, 12, 12, 12), utc),
|
||||||
'operation_id': 1234,
|
'operation_id': 1234,
|
||||||
'location': 'Jita',
|
'location': 'Jita',
|
||||||
'location_info': '4-4',
|
'location_info': '4-4',
|
||||||
@ -208,7 +209,7 @@ class FleetupManagerTestCase(TestCase):
|
|||||||
FleetUpManager.GROUP_ID)
|
FleetUpManager.GROUP_ID)
|
||||||
expected_result = {
|
expected_result = {
|
||||||
'2017-05-06 11:11:11': {
|
'2017-05-06 11:11:11': {
|
||||||
'expires': datetime.datetime(2017, 5, 6, 11, 11, 11),
|
'expires': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc),
|
||||||
'solarsystem': 'Jita',
|
'solarsystem': 'Jita',
|
||||||
'planet': '4',
|
'planet': '4',
|
||||||
'moon': '4',
|
'moon': '4',
|
||||||
@ -361,7 +362,7 @@ class FleetupManagerTestCase(TestCase):
|
|||||||
'estimated': 500000000,
|
'estimated': 500000000,
|
||||||
'faction': 'Amarr',
|
'faction': 'Amarr',
|
||||||
'categories': ["Armor", "Laser"],
|
'categories': ["Armor", "Laser"],
|
||||||
'last_update': datetime.datetime(2017, 5, 6, 11, 11, 11)
|
'last_update': make_aware(datetime.datetime(2017, 5, 6, 11, 11, 11), utc)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.assertDictEqual(expected_result, result)
|
self.assertDictEqual(expected_result, result)
|
||||||
|
@ -6,9 +6,22 @@ from hrapplications.models import ApplicationQuestion
|
|||||||
from hrapplications.models import ApplicationForm
|
from hrapplications.models import ApplicationForm
|
||||||
from hrapplications.models import ApplicationResponse
|
from hrapplications.models import ApplicationResponse
|
||||||
from hrapplications.models import ApplicationComment
|
from hrapplications.models import ApplicationComment
|
||||||
|
from hrapplications.models import ApplicationChoice
|
||||||
|
|
||||||
|
class ChoiceInline(admin.TabularInline):
|
||||||
|
model = ApplicationChoice
|
||||||
|
extra = 0
|
||||||
|
verbose_name_plural = 'Choices (optional)'
|
||||||
|
verbose_name= 'Choice'
|
||||||
|
|
||||||
|
class QuestionAdmin(admin.ModelAdmin):
|
||||||
|
fieldsets = [
|
||||||
|
(None, {'fields': ['title', 'help_text']}),
|
||||||
|
]
|
||||||
|
inlines = [ChoiceInline]
|
||||||
|
|
||||||
admin.site.register(Application)
|
admin.site.register(Application)
|
||||||
admin.site.register(ApplicationComment)
|
admin.site.register(ApplicationComment)
|
||||||
admin.site.register(ApplicationQuestion)
|
admin.site.register(ApplicationQuestion, QuestionAdmin)
|
||||||
|
admin.site.register(ApplicationForm)
|
||||||
admin.site.register(ApplicationResponse)
|
admin.site.register(ApplicationResponse)
|
||||||
admin.site.register(ApplicationForm)
|
|
33
hrapplications/migrations/0002_choices_for_questions.py
Normal file
33
hrapplications/migrations/0002_choices_for_questions.py
Normal file
@ -0,0 +1,33 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.11.4 on 2017-08-23 19:46
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations, models
|
||||||
|
import django.db.models.deletion
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('hrapplications', '0001_initial'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.CreateModel(
|
||||||
|
name='ApplicationChoice',
|
||||||
|
fields=[
|
||||||
|
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
|
||||||
|
('choice_text', models.CharField(max_length=200, verbose_name='Choice')),
|
||||||
|
],
|
||||||
|
),
|
||||||
|
migrations.AlterField(
|
||||||
|
model_name='applicationquestion',
|
||||||
|
name='title',
|
||||||
|
field=models.CharField(max_length=254, verbose_name='Question'),
|
||||||
|
),
|
||||||
|
migrations.AddField(
|
||||||
|
model_name='applicationchoice',
|
||||||
|
name='question',
|
||||||
|
field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='choices', to='hrapplications.ApplicationQuestion'),
|
||||||
|
),
|
||||||
|
]
|
@ -9,13 +9,21 @@ from eveonline.models import EveCorporationInfo
|
|||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class ApplicationQuestion(models.Model):
|
class ApplicationQuestion(models.Model):
|
||||||
title = models.CharField(max_length=254)
|
title = models.CharField(max_length=254, verbose_name='Question')
|
||||||
help_text = models.CharField(max_length=254, blank=True, null=True)
|
help_text = models.CharField(max_length=254, blank=True, null=True)
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "Question: " + self.title
|
return "Question: " + self.title
|
||||||
|
|
||||||
|
|
||||||
|
@python_2_unicode_compatible
|
||||||
|
class ApplicationChoice(models.Model):
|
||||||
|
question = models.ForeignKey(ApplicationQuestion,on_delete=models.CASCADE,related_name="choices")
|
||||||
|
choice_text = models.CharField(max_length=200, verbose_name='Choice')
|
||||||
|
|
||||||
|
def __str__(self):
|
||||||
|
return self.choice_text
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
@python_2_unicode_compatible
|
||||||
class ApplicationForm(models.Model):
|
class ApplicationForm(models.Model):
|
||||||
questions = SortedManyToManyField(ApplicationQuestion)
|
questions = SortedManyToManyField(ApplicationQuestion)
|
||||||
|
@ -5,29 +5,33 @@
|
|||||||
{% block title %}Apply To {{ corp.corporation_name }}{% endblock title %}
|
{% block title %}Apply To {{ corp.corporation_name }}{% endblock title %}
|
||||||
{% block page_title %}{% trans "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}
|
{% block page_title %}{% trans "Apply To" %} {{ corp.corporation_name }}{% endblock page_title %}
|
||||||
{% block content %}
|
{% block content %}
|
||||||
<div class="col-lg-12">
|
<div class="col-lg-12">
|
||||||
<h1 class="page-header text-center">{% trans "Apply To" %} {{ corp.corporation_name }}</h1>
|
<h1 class="page-header text-center">{% trans "Apply To" %} {{ corp.corporation_name }}</h1>
|
||||||
<div class="container-fluid">
|
<div class="container-fluid">
|
||||||
<div class="col-md-4 col-md-offset-4">
|
<div class="col-md-4 col-md-offset-4">
|
||||||
<div class="row">
|
<div class="row">
|
||||||
<form class="form-signin">
|
<form class="form-signin">
|
||||||
{% csrf_token %}
|
{% csrf_token %}
|
||||||
{% for question in questions %}
|
{% for question in questions %}
|
||||||
<div class="form-group">
|
<div class="form-group">
|
||||||
<label class="control-label" for="id_{{ question.pk }}">{{ question.title }}</label>
|
<label class="control-label" for="id_{{ question.pk }}">{{ question.title }}</label>
|
||||||
<div class=" ">
|
<div class=" ">
|
||||||
{% if question.help_text %}
|
{% if question.help_text %}
|
||||||
<div cass="text-center">{{ question.help_text }}</div>
|
<div cass="text-center">{{ question.help_text }}</div>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
<textarea class="form-control" cols="40" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="10"></textarea>
|
{% for choice in question.choices.all %}
|
||||||
</div>
|
<input type="radio" name="{{ question.pk }}" id="id_{{ question.pk }}" value="{{ choice.choice_text }}" />
|
||||||
</div>
|
<label for="choice{{ forloop.counter }}">{{ choice.choice_text }}</label><br />
|
||||||
{% endfor %}
|
{% empty %}
|
||||||
<button class="btn btn-lg btn-primary btn-block" type="submit" formmethod="post">{% trans "Submit" %}</button>
|
<textarea class="form-control" cols="30" id="id_{{ question.pk }}" name="{{ question.pk }}" rows="4"></textarea>
|
||||||
</form>
|
{% endfor %}
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
|
{% endfor %}
|
||||||
|
<button class="btn btn-lg btn-primary btn-block" type="submit" formmethod="post">{% trans "Submit" %}</button>
|
||||||
|
</form>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
</div>
|
||||||
{% endblock %}
|
{% endblock %}
|
||||||
|
|
||||||
|
@ -15,15 +15,11 @@ celery>=4.0.2
|
|||||||
django>=1.10,<2.0
|
django>=1.10,<2.0
|
||||||
django-bootstrap-form
|
django-bootstrap-form
|
||||||
django-bootstrap-pagination
|
django-bootstrap-pagination
|
||||||
django-redis>=4.4
|
|
||||||
django-registration
|
django-registration
|
||||||
django-sortedm2m
|
django-sortedm2m
|
||||||
|
django-redis-cache>=1.7.1
|
||||||
|
django-recaptcha
|
||||||
django-celery-beat
|
django-celery-beat
|
||||||
git+https://github.com/adarnof/django-navhelper
|
|
||||||
|
|
||||||
# awating release for fix to celery/django-celery#447
|
|
||||||
# django-celery
|
|
||||||
git+https://github.com/celery/django-celery
|
|
||||||
|
|
||||||
# awating pyghassen/openfire-restapi #1 to fix installation issues
|
# awating pyghassen/openfire-restapi #1 to fix installation issues
|
||||||
git+https://github.com/adarnof/openfire-restapi
|
git+https://github.com/adarnof/openfire-restapi
|
||||||
|
@ -1,5 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
from django.contrib import admin
|
|
||||||
from services.models import GroupCache
|
|
||||||
|
|
||||||
admin.site.register(GroupCache)
|
|
18
services/migrations/0003_delete_groupcache.py
Normal file
18
services/migrations/0003_delete_groupcache.py
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
# Generated by Django 1.10.5 on 2017-09-02 06:07
|
||||||
|
from __future__ import unicode_literals
|
||||||
|
|
||||||
|
from django.db import migrations
|
||||||
|
|
||||||
|
|
||||||
|
class Migration(migrations.Migration):
|
||||||
|
|
||||||
|
dependencies = [
|
||||||
|
('services', '0002_auto_20161016_0135'),
|
||||||
|
]
|
||||||
|
|
||||||
|
operations = [
|
||||||
|
migrations.DeleteModel(
|
||||||
|
name='GroupCache',
|
||||||
|
),
|
||||||
|
]
|
@ -1,18 +0,0 @@
|
|||||||
from __future__ import unicode_literals
|
|
||||||
from django.utils.encoding import python_2_unicode_compatible
|
|
||||||
from django.db import models
|
|
||||||
|
|
||||||
|
|
||||||
@python_2_unicode_compatible
|
|
||||||
class GroupCache(models.Model):
|
|
||||||
SERVICE_CHOICES = (
|
|
||||||
("discourse", "discourse"),
|
|
||||||
("discord", "discord"),
|
|
||||||
)
|
|
||||||
|
|
||||||
created = models.DateTimeField(auto_now_add=True)
|
|
||||||
groups = models.TextField(default={})
|
|
||||||
service = models.CharField(max_length=254, choices=SERVICE_CHOICES, unique=True)
|
|
||||||
|
|
||||||
def __str__(self):
|
|
||||||
return self.service
|
|
@ -3,14 +3,13 @@ import requests
|
|||||||
import json
|
import json
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from services.models import GroupCache
|
|
||||||
from requests_oauthlib import OAuth2Session
|
from requests_oauthlib import OAuth2Session
|
||||||
from functools import wraps
|
from functools import wraps
|
||||||
import logging
|
import logging
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
from django.utils import timezone
|
|
||||||
from django.core.cache import cache
|
from django.core.cache import cache
|
||||||
|
from hashlib import md5
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
@ -20,9 +19,13 @@ EVE_IMAGE_SERVER = "https://image.eveonline.com"
|
|||||||
AUTH_URL = "https://discordapp.com/api/oauth2/authorize"
|
AUTH_URL = "https://discordapp.com/api/oauth2/authorize"
|
||||||
TOKEN_URL = "https://discordapp.com/api/oauth2/token"
|
TOKEN_URL = "https://discordapp.com/api/oauth2/token"
|
||||||
|
|
||||||
# needs administrator, since Discord can't get their permissions system to work
|
"""
|
||||||
# was kick members, manage roles, manage nicknames
|
Previously all we asked for was permission to kick members, manage roles, and manage nicknames.
|
||||||
#BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
|
Users have reported weird unauthorized errors we don't understand. So now we ask for full server admin.
|
||||||
|
It's almost fixed the problem.
|
||||||
|
"""
|
||||||
|
# kick members, manage roles, manage nicknames
|
||||||
|
# BOT_PERMISSIONS = 0x00000002 + 0x10000000 + 0x08000000
|
||||||
BOT_PERMISSIONS = 0x00000008
|
BOT_PERMISSIONS = 0x00000008
|
||||||
|
|
||||||
# get user ID, accept invite
|
# get user ID, accept invite
|
||||||
@ -31,7 +34,7 @@ SCOPES = [
|
|||||||
'guilds.join',
|
'guilds.join',
|
||||||
]
|
]
|
||||||
|
|
||||||
GROUP_CACHE_MAX_AGE = datetime.timedelta(minutes=30)
|
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCORD_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # 2 hours default
|
||||||
|
|
||||||
|
|
||||||
class DiscordApiException(Exception):
|
class DiscordApiException(Exception):
|
||||||
@ -110,16 +113,16 @@ def api_backoff(func):
|
|||||||
break
|
break
|
||||||
except requests.HTTPError as e:
|
except requests.HTTPError as e:
|
||||||
if e.response.status_code == 429:
|
if e.response.status_code == 429:
|
||||||
if 'Retry-After' in e.response.headers:
|
try:
|
||||||
retry_after = e.response.headers['Retry-After']
|
retry_after = int(e.response.headers['Retry-After'])
|
||||||
else:
|
except (TypeError, KeyError):
|
||||||
# Pick some random time
|
# Pick some random time
|
||||||
retry_after = 5
|
retry_after = 5
|
||||||
|
|
||||||
logger.info("Received backoff from API of %s seconds, handling" % retry_after)
|
logger.info("Received backoff from API of %s seconds, handling" % retry_after)
|
||||||
# Store value in redis
|
# Store value in redis
|
||||||
backoff_until = (datetime.datetime.utcnow() +
|
backoff_until = (datetime.datetime.utcnow() +
|
||||||
datetime.timedelta(seconds=int(retry_after)))
|
datetime.timedelta(seconds=retry_after))
|
||||||
global_backoff = bool(e.response.headers.get('X-RateLimit-Global', False))
|
global_backoff = bool(e.response.headers.get('X-RateLimit-Global', False))
|
||||||
if global_backoff:
|
if global_backoff:
|
||||||
logger.info("Global backoff!!")
|
logger.info("Global backoff!!")
|
||||||
@ -150,10 +153,14 @@ class DiscordOAuthManager:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sanitize_name(name):
|
||||||
|
return re.sub('[^\w.-]', '', name)[:32]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_groupname(name):
|
def _sanitize_groupname(name):
|
||||||
name = name.strip(' _')
|
name = name.strip(' _')
|
||||||
return re.sub('[^\w.-]', '', name)
|
return DiscordOAuthManager._sanitize_name(name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def generate_bot_add_url():
|
def generate_bot_add_url():
|
||||||
@ -198,8 +205,9 @@ class DiscordOAuthManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def update_nickname(user_id, nickname):
|
def update_nickname(user_id, nickname):
|
||||||
try:
|
try:
|
||||||
|
nickname = DiscordOAuthManager._sanitize_name(nickname)
|
||||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
data = {'nick': nickname, }
|
data = {'nick': nickname}
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||||
r = requests.patch(path, headers=custom_headers, json=data)
|
r = requests.patch(path, headers=custom_headers, json=data)
|
||||||
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
|
logger.debug("Got status code %s after setting nickname for Discord user ID %s (%s)" % (
|
||||||
@ -230,7 +238,7 @@ class DiscordOAuthManager:
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_groups():
|
def _get_groups():
|
||||||
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
custom_headers = {'accept': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/roles"
|
||||||
r = requests.get(path, headers=custom_headers)
|
r = requests.get(path, headers=custom_headers)
|
||||||
@ -239,41 +247,20 @@ class DiscordOAuthManager:
|
|||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __update_group_cache():
|
def _generate_cache_role_key(name):
|
||||||
GroupCache.objects.filter(service="discord").delete()
|
return 'DISCORD_ROLE_NAME__%s' % md5(str(name).encode('utf-8')).hexdigest()
|
||||||
cache = GroupCache.objects.create(service="discord")
|
|
||||||
cache.groups = json.dumps(DiscordOAuthManager.__get_groups())
|
|
||||||
cache.save()
|
|
||||||
return cache
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_group_cache():
|
def _group_name_to_id(name):
|
||||||
if not GroupCache.objects.filter(service="discord").exists():
|
name = DiscordOAuthManager._sanitize_groupname(name)
|
||||||
DiscordOAuthManager.__update_group_cache()
|
|
||||||
cache = GroupCache.objects.get(service="discord")
|
|
||||||
age = timezone.now() - cache.created
|
|
||||||
if age > GROUP_CACHE_MAX_AGE:
|
|
||||||
logger.debug("Group cache has expired. Triggering update.")
|
|
||||||
cache = DiscordOAuthManager.__update_group_cache()
|
|
||||||
return json.loads(cache.groups)
|
|
||||||
|
|
||||||
@staticmethod
|
def get_or_make_role():
|
||||||
def __group_name_to_id(name):
|
groups = DiscordOAuthManager._get_groups()
|
||||||
cache = DiscordOAuthManager.__get_group_cache()
|
for g in groups:
|
||||||
for g in cache:
|
if g['name'] == name:
|
||||||
if g['name'] == name:
|
return g['id']
|
||||||
return g['id']
|
return DiscordOAuthManager._create_group(name)['id']
|
||||||
logger.debug("Group %s not found on Discord. Creating" % name)
|
return cache.get_or_set(DiscordOAuthManager._generate_cache_role_key(name), get_or_make_role, GROUP_CACHE_MAX_AGE)
|
||||||
DiscordOAuthManager.__create_group(name)
|
|
||||||
return DiscordOAuthManager.__group_name_to_id(name)
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __group_id_to_name(id):
|
|
||||||
cache = DiscordOAuthManager.__get_group_cache()
|
|
||||||
for g in cache:
|
|
||||||
if g['id'] == id:
|
|
||||||
return g['name']
|
|
||||||
raise KeyError("Group ID %s not found on Discord" % id)
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __generate_role():
|
def __generate_role():
|
||||||
@ -300,16 +287,15 @@ class DiscordOAuthManager:
|
|||||||
return r.json()
|
return r.json()
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __create_group(name):
|
def _create_group(name):
|
||||||
role = DiscordOAuthManager.__generate_role()
|
role = DiscordOAuthManager.__generate_role()
|
||||||
DiscordOAuthManager.__edit_role(role['id'], name)
|
return DiscordOAuthManager.__edit_role(role['id'], name)
|
||||||
DiscordOAuthManager.__update_group_cache()
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@api_backoff
|
@api_backoff
|
||||||
def update_groups(user_id, groups):
|
def update_groups(user_id, groups):
|
||||||
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
custom_headers = {'content-type': 'application/json', 'authorization': 'Bot ' + settings.DISCORD_BOT_TOKEN}
|
||||||
group_ids = [DiscordOAuthManager.__group_name_to_id(DiscordOAuthManager._sanitize_groupname(g)) for g in groups]
|
group_ids = [DiscordOAuthManager._group_name_to_id(DiscordOAuthManager._sanitize_groupname(g)) for g in groups]
|
||||||
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
path = DISCORD_URL + "/guilds/" + str(settings.DISCORD_GUILD_ID) + "/members/" + str(user_id)
|
||||||
data = {'roles': group_ids}
|
data = {'roles': group_ids}
|
||||||
r = requests.patch(path, headers=custom_headers, json=data)
|
r = requests.patch(path, headers=custom_headers, json=data)
|
||||||
|
@ -9,7 +9,6 @@ from django.core.exceptions import ObjectDoesNotExist
|
|||||||
|
|
||||||
from notifications import notify
|
from notifications import notify
|
||||||
from services.modules.discord.manager import DiscordOAuthManager, DiscordApiBackoff
|
from services.modules.discord.manager import DiscordOAuthManager, DiscordApiBackoff
|
||||||
from services.tasks import only_one
|
|
||||||
from .models import DiscordUser
|
from .models import DiscordUser
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
@ -346,7 +346,7 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
# Assert
|
# Assert
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._DiscordOAuthManager__get_group_cache')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups(self, group_cache, m):
|
def test_update_groups(self, group_cache, m):
|
||||||
from . import manager
|
from . import manager
|
||||||
@ -380,7 +380,7 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
self.assertNotIn(444, history['roles'], 'The group id 444 must NOT be added to the request')
|
self.assertNotIn(444, history['roles'], 'The group id 444 must NOT be added to the request')
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.cache')
|
@mock.patch(MODULE_PATH + '.manager.cache')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._DiscordOAuthManager__get_group_cache')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups_backoff(self, group_cache, djcache, m):
|
def test_update_groups_backoff(self, group_cache, djcache, m):
|
||||||
from . import manager
|
from . import manager
|
||||||
@ -415,7 +415,7 @@ class DiscordManagerTestCase(TestCase):
|
|||||||
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
|
self.assertTrue(datetime.datetime.strptime(args[1], manager.cache_time_format) > datetime.datetime.now())
|
||||||
|
|
||||||
@mock.patch(MODULE_PATH + '.manager.cache')
|
@mock.patch(MODULE_PATH + '.manager.cache')
|
||||||
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._DiscordOAuthManager__get_group_cache')
|
@mock.patch(MODULE_PATH + '.manager.DiscordOAuthManager._get_groups')
|
||||||
@requests_mock.Mocker()
|
@requests_mock.Mocker()
|
||||||
def test_update_groups_global_backoff(self, group_cache, djcache, m):
|
def test_update_groups_global_backoff(self, group_cache, djcache, m):
|
||||||
from . import manager
|
from . import manager
|
||||||
|
@ -1,17 +1,15 @@
|
|||||||
from __future__ import unicode_literals
|
from __future__ import unicode_literals
|
||||||
import logging
|
import logging
|
||||||
import requests
|
import requests
|
||||||
import random
|
|
||||||
import string
|
|
||||||
import datetime
|
|
||||||
import json
|
|
||||||
import re
|
import re
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils import timezone
|
from django.core.cache import cache
|
||||||
from services.models import GroupCache
|
from hashlib import md5
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
GROUP_CACHE_MAX_AGE = int(getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60)) # default 2 hours
|
||||||
|
|
||||||
|
|
||||||
class DiscourseError(Exception):
|
class DiscourseError(Exception):
|
||||||
def __init__(self, endpoint, errors):
|
def __init__(self, endpoint, errors):
|
||||||
@ -21,12 +19,13 @@ class DiscourseError(Exception):
|
|||||||
def __str__(self):
|
def __str__(self):
|
||||||
return "API execution failed.\nErrors: %s\nEndpoint: %s" % (self.errors, self.endpoint)
|
return "API execution failed.\nErrors: %s\nEndpoint: %s" % (self.errors, self.endpoint)
|
||||||
|
|
||||||
|
|
||||||
# not exhaustive, only the ones we need
|
# not exhaustive, only the ones we need
|
||||||
ENDPOINTS = {
|
ENDPOINTS = {
|
||||||
'groups': {
|
'groups': {
|
||||||
'list': {
|
'list': {
|
||||||
'path': "/admin/groups.json",
|
'path': "/admin/groups.json",
|
||||||
'method': requests.get,
|
'method': 'get',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -34,7 +33,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'create': {
|
'create': {
|
||||||
'path': "/admin/groups",
|
'path': "/admin/groups",
|
||||||
'method': requests.post,
|
'method': 'post',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['name'],
|
'required': ['name'],
|
||||||
'optional': ['visible'],
|
'optional': ['visible'],
|
||||||
@ -42,7 +41,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'add_user': {
|
'add_user': {
|
||||||
'path': "/admin/groups/%s/members.json",
|
'path': "/admin/groups/%s/members.json",
|
||||||
'method': requests.put,
|
'method': 'put',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['usernames'],
|
'required': ['usernames'],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -50,7 +49,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'remove_user': {
|
'remove_user': {
|
||||||
'path': "/admin/groups/%s/members.json",
|
'path': "/admin/groups/%s/members.json",
|
||||||
'method': requests.delete,
|
'method': 'delete',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['username'],
|
'required': ['username'],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -58,7 +57,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'delete': {
|
'delete': {
|
||||||
'path': "/admin/groups/%s.json",
|
'path': "/admin/groups/%s.json",
|
||||||
'method': requests.delete,
|
'method': 'delete',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -68,7 +67,7 @@ ENDPOINTS = {
|
|||||||
'users': {
|
'users': {
|
||||||
'create': {
|
'create': {
|
||||||
'path': "/users",
|
'path': "/users",
|
||||||
'method': requests.post,
|
'method': 'post',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['name', 'email', 'password', 'username'],
|
'required': ['name', 'email', 'password', 'username'],
|
||||||
'optional': ['active'],
|
'optional': ['active'],
|
||||||
@ -76,7 +75,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'update': {
|
'update': {
|
||||||
'path': "/users/%s.json",
|
'path': "/users/%s.json",
|
||||||
'method': requests.put,
|
'method': 'put',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['params'],
|
'required': ['params'],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -84,7 +83,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'get': {
|
'get': {
|
||||||
'path': "/users/%s.json",
|
'path': "/users/%s.json",
|
||||||
'method': requests.get,
|
'method': 'get',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -92,7 +91,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'activate': {
|
'activate': {
|
||||||
'path': "/admin/users/%s/activate",
|
'path': "/admin/users/%s/activate",
|
||||||
'method': requests.put,
|
'method': 'put',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -100,7 +99,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'set_email': {
|
'set_email': {
|
||||||
'path': "/users/%s/preferences/email",
|
'path': "/users/%s/preferences/email",
|
||||||
'method': requests.put,
|
'method': 'put',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['email'],
|
'required': ['email'],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -108,7 +107,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'suspend': {
|
'suspend': {
|
||||||
'path': "/admin/users/%s/suspend",
|
'path': "/admin/users/%s/suspend",
|
||||||
'method': requests.put,
|
'method': 'put',
|
||||||
'args': {
|
'args': {
|
||||||
'required': ['duration', 'reason'],
|
'required': ['duration', 'reason'],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -116,7 +115,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'unsuspend': {
|
'unsuspend': {
|
||||||
'path': "/admin/users/%s/unsuspend",
|
'path': "/admin/users/%s/unsuspend",
|
||||||
'method': requests.put,
|
'method': 'put',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -124,7 +123,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'logout': {
|
'logout': {
|
||||||
'path': "/admin/users/%s/log_out",
|
'path': "/admin/users/%s/log_out",
|
||||||
'method': requests.post,
|
'method': 'post',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -132,7 +131,7 @@ ENDPOINTS = {
|
|||||||
},
|
},
|
||||||
'external': {
|
'external': {
|
||||||
'path': "/users/by-external/%s.json",
|
'path': "/users/by-external/%s.json",
|
||||||
'method': requests.get,
|
'method': 'get',
|
||||||
'args': {
|
'args': {
|
||||||
'required': [],
|
'required': [],
|
||||||
'optional': [],
|
'optional': [],
|
||||||
@ -146,8 +145,7 @@ class DiscourseManager:
|
|||||||
def __init__(self):
|
def __init__(self):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
GROUP_CACHE_MAX_AGE = datetime.timedelta(minutes=30)
|
REVOKED_EMAIL = 'revoked@localhost'
|
||||||
REVOKED_EMAIL = 'revoked@' + settings.DOMAIN
|
|
||||||
SUSPEND_DAYS = 99999
|
SUSPEND_DAYS = 99999
|
||||||
SUSPEND_REASON = "Disabled by auth."
|
SUSPEND_REASON = "Disabled by auth."
|
||||||
|
|
||||||
@ -171,7 +169,8 @@ class DiscourseManager:
|
|||||||
for arg in kwargs:
|
for arg in kwargs:
|
||||||
if arg not in endpoint['args']['required'] and arg not in endpoint['args']['optional'] and not silent:
|
if arg not in endpoint['args']['required'] and arg not in endpoint['args']['optional'] and not silent:
|
||||||
logger.warn("Received unrecognized kwarg %s for endpoint %s" % (arg, endpoint))
|
logger.warn("Received unrecognized kwarg %s for endpoint %s" % (arg, endpoint))
|
||||||
r = endpoint['method'](settings.DISCOURSE_URL + endpoint['parsed_url'], params=params, json=data)
|
r = getattr(requests, endpoint['method'])(settings.DISCOURSE_URL + endpoint['parsed_url'], params=params,
|
||||||
|
json=data)
|
||||||
try:
|
try:
|
||||||
if 'errors' in r.json() and not silent:
|
if 'errors' in r.json() and not silent:
|
||||||
logger.error("Discourse execution failed.\nEndpoint: %s\nErrors: %s" % (endpoint, r.json()['errors']))
|
logger.error("Discourse execution failed.\nEndpoint: %s\nErrors: %s" % (endpoint, r.json()['errors']))
|
||||||
@ -190,67 +189,59 @@ class DiscourseManager:
|
|||||||
return out
|
return out
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __generate_random_pass():
|
def _get_groups():
|
||||||
return ''.join([random.choice(string.ascii_letters + string.digits) for n in range(16)])
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __get_groups():
|
|
||||||
endpoint = ENDPOINTS['groups']['list']
|
endpoint = ENDPOINTS['groups']['list']
|
||||||
data = DiscourseManager.__exc(endpoint)
|
data = DiscourseManager.__exc(endpoint)
|
||||||
return [g for g in data if not g['automatic']]
|
return [g for g in data if not g['automatic']]
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __update_group_cache():
|
def _create_group(name):
|
||||||
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']
|
endpoint = ENDPOINTS['groups']['create']
|
||||||
DiscourseManager.__exc(endpoint, name=name[:20], visible=True)
|
return DiscourseManager.__exc(endpoint, name=name[:20], visible=True)['basic_group']
|
||||||
DiscourseManager.__update_group_cache()
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_cache_group_name_key(name):
|
||||||
|
return 'DISCOURSE_GROUP_NAME__%s' % md5(name.encode('utf-8')).hexdigest()
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _generate_cache_group_id_key(g_id):
|
||||||
|
return 'DISCOURSE_GROUP_ID__%s' % g_id
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __group_name_to_id(name):
|
def __group_name_to_id(name):
|
||||||
cache = DiscourseManager.__get_group_cache()
|
name = DiscourseManager._sanitize_groupname(name)
|
||||||
for g in cache:
|
|
||||||
if g['name'] == name[0:20]:
|
def get_or_create_group():
|
||||||
return g['id']
|
groups = DiscourseManager._get_groups()
|
||||||
logger.debug("Group %s not found on Discourse. Creating" % name)
|
for g in groups:
|
||||||
DiscourseManager.__create_group(name)
|
if g['name'] == name:
|
||||||
return DiscourseManager.__group_name_to_id(name)
|
return g['id']
|
||||||
|
return DiscourseManager._create_group(name)['id']
|
||||||
|
|
||||||
|
return cache.get_or_set(DiscourseManager._generate_cache_group_name_key(name), get_or_create_group,
|
||||||
|
GROUP_CACHE_MAX_AGE)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __group_id_to_name(id):
|
def __group_id_to_name(g_id):
|
||||||
cache = DiscourseManager.__get_group_cache()
|
def get_group_name():
|
||||||
for g in cache:
|
groups = DiscourseManager._get_groups()
|
||||||
if g['id'] == id:
|
for g in groups:
|
||||||
return g['name']
|
if g['id'] == g_id:
|
||||||
raise KeyError("Group ID %s not found on Discourse" % id)
|
return g['name']
|
||||||
|
raise KeyError("Group ID %s not found on Discourse" % g_id)
|
||||||
|
|
||||||
|
return cache.get_or_set(DiscourseManager._generate_cache_group_id_key(g_id), get_group_name,
|
||||||
|
GROUP_CACHE_MAX_AGE)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __add_user_to_group(id, username):
|
def __add_user_to_group(g_id, username):
|
||||||
endpoint = ENDPOINTS['groups']['add_user']
|
endpoint = ENDPOINTS['groups']['add_user']
|
||||||
DiscourseManager.__exc(endpoint, id, usernames=[username])
|
DiscourseManager.__exc(endpoint, g_id, usernames=[username])
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __remove_user_from_group(id, username):
|
def __remove_user_from_group(g_id, username):
|
||||||
endpoint = ENDPOINTS['groups']['remove_user']
|
endpoint = ENDPOINTS['groups']['remove_user']
|
||||||
DiscourseManager.__exc(endpoint, id, username=username)
|
DiscourseManager.__exc(endpoint, g_id, username=username)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __generate_group_dict(names):
|
def __generate_group_dict(names):
|
||||||
@ -269,10 +260,6 @@ class DiscourseManager:
|
|||||||
data = DiscourseManager.__get_user(name, silent=silent)
|
data = DiscourseManager.__get_user(name, silent=silent)
|
||||||
return data['user']['id']
|
return data['user']['id']
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def __user_id_to_name(id):
|
|
||||||
raise NotImplementedError
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_user(username, silent=False):
|
def __get_user(username, silent=False):
|
||||||
endpoint = ENDPOINTS['users']['get']
|
endpoint = ENDPOINTS['users']['get']
|
||||||
@ -281,14 +268,14 @@ class DiscourseManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def __activate_user(username):
|
def __activate_user(username):
|
||||||
endpoint = ENDPOINTS['users']['activate']
|
endpoint = ENDPOINTS['users']['activate']
|
||||||
id = DiscourseManager.__user_name_to_id(username)
|
u_id = DiscourseManager.__user_name_to_id(username)
|
||||||
DiscourseManager.__exc(endpoint, id)
|
DiscourseManager.__exc(endpoint, u_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __update_user(username, **kwargs):
|
def __update_user(username, **kwargs):
|
||||||
endpoint = ENDPOINTS['users']['update']
|
endpoint = ENDPOINTS['users']['update']
|
||||||
id = DiscourseManager.__user_name_to_id(username)
|
u_id = DiscourseManager.__user_name_to_id(username)
|
||||||
DiscourseManager.__exc(endpoint, id, params=kwargs)
|
DiscourseManager.__exc(endpoint, u_id, params=kwargs)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __create_user(username, email, password):
|
def __create_user(username, email, password):
|
||||||
@ -300,21 +287,21 @@ class DiscourseManager:
|
|||||||
try:
|
try:
|
||||||
DiscourseManager.__user_name_to_id(username, silent=True)
|
DiscourseManager.__user_name_to_id(username, silent=True)
|
||||||
return True
|
return True
|
||||||
except:
|
except DiscourseError:
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __suspend_user(username):
|
def __suspend_user(username):
|
||||||
id = DiscourseManager.__user_name_to_id(username)
|
u_id = DiscourseManager.__user_name_to_id(username)
|
||||||
endpoint = ENDPOINTS['users']['suspend']
|
endpoint = ENDPOINTS['users']['suspend']
|
||||||
return DiscourseManager.__exc(endpoint, id, duration=DiscourseManager.SUSPEND_DAYS,
|
return DiscourseManager.__exc(endpoint, u_id, duration=DiscourseManager.SUSPEND_DAYS,
|
||||||
reason=DiscourseManager.SUSPEND_REASON)
|
reason=DiscourseManager.SUSPEND_REASON)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __unsuspend(username):
|
def __unsuspend(username):
|
||||||
id = DiscourseManager.__user_name_to_id(username)
|
u_id = DiscourseManager.__user_name_to_id(username)
|
||||||
endpoint = ENDPOINTS['users']['unsuspend']
|
endpoint = ENDPOINTS['users']['unsuspend']
|
||||||
return DiscourseManager.__exc(endpoint, id)
|
return DiscourseManager.__exc(endpoint, u_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __set_email(username, email):
|
def __set_email(username, email):
|
||||||
@ -322,47 +309,53 @@ class DiscourseManager:
|
|||||||
return DiscourseManager.__exc(endpoint, username, email=email)
|
return DiscourseManager.__exc(endpoint, username, email=email)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __logout(id):
|
def __logout(u_id):
|
||||||
endpoint = ENDPOINTS['users']['logout']
|
endpoint = ENDPOINTS['users']['logout']
|
||||||
return DiscourseManager.__exc(endpoint, id)
|
return DiscourseManager.__exc(endpoint, u_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __get_user_by_external(id):
|
def __get_user_by_external(u_id):
|
||||||
endpoint = ENDPOINTS['users']['external']
|
endpoint = ENDPOINTS['users']['external']
|
||||||
return DiscourseManager.__exc(endpoint, id)
|
return DiscourseManager.__exc(endpoint, u_id)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def __user_id_by_external_id(id):
|
def __user_id_by_external_id(u_id):
|
||||||
data = DiscourseManager.__get_user_by_external(id)
|
data = DiscourseManager.__get_user_by_external(u_id)
|
||||||
return data['user']['id']
|
return data['user']['id']
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _sanitize_name(name):
|
||||||
|
name = name.replace(' ', '_')
|
||||||
|
name = name.replace("'", '')
|
||||||
|
name = name.lstrip(' _')
|
||||||
|
name = name[:20]
|
||||||
|
name = name.rstrip(' _')
|
||||||
|
return name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_username(username):
|
def _sanitize_username(username):
|
||||||
sanitized = username.replace(" ", "_")
|
return DiscourseManager._sanitize_name(username)
|
||||||
sanitized = sanitized.strip(' _')
|
|
||||||
sanitized = sanitized.replace("'", "")
|
|
||||||
return sanitized
|
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_groupname(name):
|
def _sanitize_groupname(name):
|
||||||
name = name.strip(' _')
|
|
||||||
name = re.sub('[^\w]', '', name)
|
name = re.sub('[^\w]', '', name)
|
||||||
|
name = DiscourseManager._sanitize_name(name)
|
||||||
if len(name) < 3:
|
if len(name) < 3:
|
||||||
name = name + "".join('_' for i in range(3-len(name)))
|
name = "Group " + name
|
||||||
return name[:20]
|
return name
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def update_groups(user):
|
def update_groups(user):
|
||||||
groups = []
|
groups = []
|
||||||
for g in user.groups.all():
|
for g in user.groups.all():
|
||||||
groups.append(DiscourseManager._sanitize_groupname(str(g)[:20]))
|
groups.append(DiscourseManager._sanitize_groupname(str(g)))
|
||||||
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
|
logger.debug("Updating discourse user %s groups to %s" % (user, groups))
|
||||||
group_dict = DiscourseManager.__generate_group_dict(groups)
|
group_dict = DiscourseManager.__generate_group_dict(groups)
|
||||||
inv_group_dict = {v: k for k, v in group_dict.items()}
|
inv_group_dict = {v: k for k, v in group_dict.items()}
|
||||||
username = DiscourseManager.__get_user_by_external(user.pk)['user']['username']
|
username = DiscourseManager.__get_user_by_external(user.pk)['user']['username']
|
||||||
user_groups = DiscourseManager.__get_user_groups(username)
|
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]
|
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 x in inv_group_dict]
|
rem_groups = [x for x in user_groups if x not in inv_group_dict]
|
||||||
if add_groups or rem_groups:
|
if add_groups or rem_groups:
|
||||||
logger.info(
|
logger.info(
|
||||||
"Updating discourse user %s groups: adding %s, removing %s" % (username, add_groups, rem_groups))
|
"Updating discourse user %s groups: adding %s, removing %s" % (username, add_groups, rem_groups))
|
||||||
|
@ -5,8 +5,6 @@ from django.contrib.auth.models import User
|
|||||||
from django.core.exceptions import ObjectDoesNotExist
|
from django.core.exceptions import ObjectDoesNotExist
|
||||||
from notifications import notify
|
from notifications import notify
|
||||||
|
|
||||||
from services.tasks import only_one
|
|
||||||
|
|
||||||
from .manager import DiscourseManager
|
from .manager import DiscourseManager
|
||||||
from .models import DiscourseUser
|
from .models import DiscourseUser
|
||||||
|
|
||||||
|
@ -61,7 +61,7 @@ class OpenfireManager:
|
|||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def _sanitize_groupname(name):
|
def _sanitize_groupname(name):
|
||||||
name = name.strip(' _')
|
name = name.strip(' _').lower()
|
||||||
return re.sub('[^\w.-]', '', name)
|
return re.sub('[^\w.-]', '', name)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
@ -120,9 +120,10 @@ class OpenfireManager:
|
|||||||
logger.error("Unable to update openfire user %s password - user not found on server." % username)
|
logger.error("Unable to update openfire user %s password - user not found on server." % username)
|
||||||
return ""
|
return ""
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def update_user_groups(username, groups):
|
def update_user_groups(cls, username, groups):
|
||||||
logger.debug("Updating openfire user %s groups %s" % (username, groups))
|
logger.debug("Updating openfire user %s groups %s" % (username, groups))
|
||||||
|
s_groups = list(map(cls._sanitize_groupname, groups)) # Sanitized group names
|
||||||
api = ofUsers(settings.OPENFIRE_ADDRESS, settings.OPENFIRE_SECRET_KEY)
|
api = ofUsers(settings.OPENFIRE_ADDRESS, settings.OPENFIRE_SECRET_KEY)
|
||||||
response = api.get_user_groups(username)
|
response = api.get_user_groups(username)
|
||||||
remote_groups = []
|
remote_groups = []
|
||||||
@ -130,16 +131,15 @@ class OpenfireManager:
|
|||||||
remote_groups = response['groupname']
|
remote_groups = response['groupname']
|
||||||
if isinstance(remote_groups, six.string_types):
|
if isinstance(remote_groups, six.string_types):
|
||||||
remote_groups = [remote_groups]
|
remote_groups = [remote_groups]
|
||||||
|
remote_groups = list(map(cls._sanitize_groupname, remote_groups))
|
||||||
logger.debug("Openfire user %s has groups %s" % (username, remote_groups))
|
logger.debug("Openfire user %s has groups %s" % (username, remote_groups))
|
||||||
add_groups = []
|
add_groups = []
|
||||||
del_groups = []
|
del_groups = []
|
||||||
for g in groups:
|
for g in s_groups:
|
||||||
g = OpenfireManager._sanitize_groupname(g)
|
|
||||||
if g not in remote_groups:
|
if g not in remote_groups:
|
||||||
add_groups.append(g)
|
add_groups.append(g)
|
||||||
for g in remote_groups:
|
for g in remote_groups:
|
||||||
g = OpenfireManager._sanitize_groupname(g)
|
if g not in s_groups:
|
||||||
if g not in groups:
|
|
||||||
del_groups.append(g)
|
del_groups.append(g)
|
||||||
logger.info(
|
logger.info(
|
||||||
"Updating openfire groups for user %s - adding %s, removing %s" % (username, add_groups, del_groups))
|
"Updating openfire groups for user %s - adding %s, removing %s" % (username, add_groups, del_groups))
|
||||||
@ -155,10 +155,11 @@ class OpenfireManager:
|
|||||||
api.delete_user_groups(username, groups)
|
api.delete_user_groups(username, groups)
|
||||||
logger.info("Deleted groups %s from openfire user %s" % (groups, username))
|
logger.info("Deleted groups %s from openfire user %s" % (groups, username))
|
||||||
|
|
||||||
@staticmethod
|
@classmethod
|
||||||
def send_broadcast_message(group_name, broadcast_message):
|
def send_broadcast_message(cls, group_name, broadcast_message):
|
||||||
logger.debug("Sending jabber ping to group %s with message %s" % (group_name, broadcast_message))
|
s_group_name = cls._sanitize_groupname(group_name)
|
||||||
to_address = group_name + '@' + settings.BROADCAST_SERVICE_NAME + '.' + settings.JABBER_URL
|
logger.debug("Sending jabber ping to group %s with message %s" % (s_group_name, broadcast_message))
|
||||||
|
to_address = s_group_name + '@' + settings.BROADCAST_SERVICE_NAME + '.' + settings.JABBER_URL
|
||||||
xmpp = PingBot(settings.BROADCAST_USER, settings.BROADCAST_USER_PASSWORD, to_address, broadcast_message)
|
xmpp = PingBot(settings.BROADCAST_USER, settings.BROADCAST_USER_PASSWORD, to_address, broadcast_message)
|
||||||
xmpp.register_plugin('xep_0030') # Service Discovery
|
xmpp.register_plugin('xep_0030') # Service Discovery
|
||||||
xmpp.register_plugin('xep_0199') # XMPP Ping
|
xmpp.register_plugin('xep_0199') # XMPP Ping
|
||||||
|
@ -211,3 +211,28 @@ class OpenfireManagerTestCase(TestCase):
|
|||||||
result_username = self.manager._OpenfireManager__sanitize_username(test_username)
|
result_username = self.manager._OpenfireManager__sanitize_username(test_username)
|
||||||
|
|
||||||
self.assertEqual(result_username, 'My_Test\\20User\\22\\27\\26\\2f\\3a\\3c\\3e\\40name\\5c20name')
|
self.assertEqual(result_username, 'My_Test\\20User\\22\\27\\26\\2f\\3a\\3c\\3e\\40name\\5c20name')
|
||||||
|
|
||||||
|
def test__sanitize_groupname(self):
|
||||||
|
test_groupname = " My_Test Groupname"
|
||||||
|
|
||||||
|
result_groupname = self.manager._sanitize_groupname(test_groupname)
|
||||||
|
|
||||||
|
self.assertEqual(result_groupname, "my_testgroupname")
|
||||||
|
|
||||||
|
@mock.patch(MODULE_PATH + '.manager.ofUsers')
|
||||||
|
def test_update_user_groups(self, api):
|
||||||
|
groups = ["AddGroup", "othergroup", "Guest Group"]
|
||||||
|
server_groups = ["othergroup", "Guest Group", "REMOVE group"]
|
||||||
|
username = "testuser"
|
||||||
|
api_instance = api.return_value
|
||||||
|
api_instance.get_user_groups.return_value = {'groupname': server_groups}
|
||||||
|
|
||||||
|
self.manager.update_user_groups(username, groups)
|
||||||
|
|
||||||
|
self.assertTrue(api_instance.add_user_groups.called)
|
||||||
|
args, kwargs = api_instance.add_user_groups.call_args
|
||||||
|
self.assertEqual(args[1], ["addgroup"])
|
||||||
|
|
||||||
|
self.assertTrue(api_instance.delete_user_groups.called)
|
||||||
|
args, kwargs = api_instance.delete_user_groups.call_args
|
||||||
|
self.assertEqual(args[1], ["removegroup"])
|
||||||
|
@ -63,7 +63,7 @@ class SeatManager:
|
|||||||
logger.info("Added SeAT user with username %s" % sanitized)
|
logger.info("Added SeAT user with username %s" % sanitized)
|
||||||
return sanitized, password
|
return sanitized, password
|
||||||
logger.info("Failed to add SeAT user with username %s" % sanitized)
|
logger.info("Failed to add SeAT user with username %s" % sanitized)
|
||||||
return None
|
return None, None
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_user(cls, username):
|
def delete_user(cls, username):
|
||||||
@ -75,25 +75,6 @@ class SeatManager:
|
|||||||
return username
|
return username
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@classmethod
|
|
||||||
def disable_user(cls, username):
|
|
||||||
""" Disable user """
|
|
||||||
ret = cls.exec_request('user/{}'.format(username), 'put', active=0)
|
|
||||||
logger.debug(ret)
|
|
||||||
ret = cls.exec_request('user/{}'.format(username), 'put', email="")
|
|
||||||
logger.debug(ret)
|
|
||||||
if cls._response_ok(ret):
|
|
||||||
try:
|
|
||||||
cls.update_roles(username, [])
|
|
||||||
logger.info("Disabled SeAT user with username %s" % username)
|
|
||||||
return username
|
|
||||||
except KeyError:
|
|
||||||
# if something goes wrong, delete user from seat instead of disabling
|
|
||||||
if cls.delete_user(username):
|
|
||||||
return username
|
|
||||||
logger.info("Failed to disabled SeAT user with username %s" % username)
|
|
||||||
return None
|
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def enable_user(cls, username):
|
def enable_user(cls, username):
|
||||||
""" Enable user """
|
""" Enable user """
|
||||||
@ -105,14 +86,22 @@ class SeatManager:
|
|||||||
logger.info("Failed to enabled SeAT user with username %s" % username)
|
logger.info("Failed to enabled SeAT user with username %s" % username)
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
@classmethod
|
||||||
|
def _check_email_changed(cls, username, email):
|
||||||
|
"""Compares email to one set on SeAT"""
|
||||||
|
ret = cls.exec_request('user/{}'.format(username), 'get', raise_for_status=True)
|
||||||
|
return ret['email'] != email
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def update_user(cls, username, email, password):
|
def update_user(cls, username, email, password):
|
||||||
""" Edit user info """
|
""" Edit user info """
|
||||||
logger.debug("Updating SeAT username %s with email %s and password" % (username, email))
|
if cls._check_email_changed(username, email):
|
||||||
ret = cls.exec_request('user/{}'.format(username), 'put', email=email)
|
# if we try to set the email to whatever it is already on SeAT, we get a HTTP422 error
|
||||||
logger.debug(ret)
|
logger.debug("Updating SeAT username %s with email %s and password" % (username, email))
|
||||||
if not cls._response_ok(ret):
|
ret = cls.exec_request('user/{}'.format(username), 'put', email=email)
|
||||||
logger.warn("Failed to update email for username {}".format(username))
|
logger.debug(ret)
|
||||||
|
if not cls._response_ok(ret):
|
||||||
|
logger.warn("Failed to update email for username {}".format(username))
|
||||||
ret = cls.exec_request('user/{}'.format(username), 'put', password=password)
|
ret = cls.exec_request('user/{}'.format(username), 'put', password=password)
|
||||||
logger.debug(ret)
|
logger.debug(ret)
|
||||||
if not cls._response_ok(ret):
|
if not cls._response_ok(ret):
|
||||||
@ -275,5 +264,5 @@ class SeatManager:
|
|||||||
@staticmethod
|
@staticmethod
|
||||||
def username_hash(username):
|
def username_hash(username):
|
||||||
m = hashlib.sha1()
|
m = hashlib.sha1()
|
||||||
m.update(username)
|
m.update(username.encode('utf-8'))
|
||||||
return m.hexdigest()
|
return m.hexdigest()
|
||||||
|
@ -28,7 +28,7 @@ class SeatTasks:
|
|||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def delete_user(cls, user, notify_user=False):
|
def delete_user(cls, user, notify_user=False):
|
||||||
if cls.has_account(user) and SeatManager.disable_user(user.seat.username):
|
if cls.has_account(user) and SeatManager.delete_user(user.seat.username):
|
||||||
user.seat.delete()
|
user.seat.delete()
|
||||||
logger.info("Successfully deactivated SeAT for user %s" % user)
|
logger.info("Successfully deactivated SeAT for user %s" % user)
|
||||||
if notify_user:
|
if notify_user:
|
||||||
|
@ -92,10 +92,10 @@ class SeatHooksTestCase(TestCase):
|
|||||||
|
|
||||||
# Test none user is deleted
|
# Test none user is deleted
|
||||||
none_user = User.objects.get(username=self.none_user)
|
none_user = User.objects.get(username=self.none_user)
|
||||||
manager.disable_user.return_value = 'abc123'
|
manager.delete_user.return_value = 'abc123'
|
||||||
SeatUser.objects.create(user=none_user, username='abc123')
|
SeatUser.objects.create(user=none_user, username='abc123')
|
||||||
service.validate_user(none_user)
|
service.validate_user(none_user)
|
||||||
self.assertTrue(manager.disable_user.called)
|
self.assertTrue(manager.delete_user.called)
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
none_seat = User.objects.get(username=self.none_user).seat
|
none_seat = User.objects.get(username=self.none_user).seat
|
||||||
|
|
||||||
@ -107,7 +107,7 @@ class SeatHooksTestCase(TestCase):
|
|||||||
result = service.delete_user(member)
|
result = service.delete_user(member)
|
||||||
|
|
||||||
self.assertTrue(result)
|
self.assertTrue(result)
|
||||||
self.assertTrue(manager.disable_user.called)
|
self.assertTrue(manager.delete_user.called)
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
seat_user = User.objects.get(username=self.member).seat
|
seat_user = User.objects.get(username=self.member).seat
|
||||||
|
|
||||||
@ -169,7 +169,7 @@ class SeatViewsTestCase(TestCase):
|
|||||||
|
|
||||||
response = self.client.get(urls.reverse('auth_deactivate_seat'))
|
response = self.client.get(urls.reverse('auth_deactivate_seat'))
|
||||||
|
|
||||||
self.assertTrue(manager.disable_user.called)
|
self.assertTrue(manager.delete_user.called)
|
||||||
self.assertRedirects(response, expected_url=urls.reverse('auth_services'), target_status_code=200)
|
self.assertRedirects(response, expected_url=urls.reverse('auth_services'), target_status_code=200)
|
||||||
with self.assertRaises(ObjectDoesNotExist):
|
with self.assertRaises(ObjectDoesNotExist):
|
||||||
seat_user = User.objects.get(pk=self.member.pk).seat
|
seat_user = User.objects.get(pk=self.member.pk).seat
|
||||||
|
@ -217,6 +217,10 @@ class Teamspeak3Manager:
|
|||||||
logger.debug("Deleting user %s with id %s from TS3 server." % (user, uid))
|
logger.debug("Deleting user %s with id %s from TS3 server." % (user, uid))
|
||||||
if user:
|
if user:
|
||||||
clients = self.server.send_command('clientlist')
|
clients = self.server.send_command('clientlist')
|
||||||
|
if isinstance(clients, dict):
|
||||||
|
# Rewrap list
|
||||||
|
clients = [clients]
|
||||||
|
|
||||||
for client in clients:
|
for client in clients:
|
||||||
try:
|
try:
|
||||||
if client['keys']['client_database_id'] == user:
|
if client['keys']['client_database_id'] == user:
|
||||||
|
10
thirdparty/Supervisor/auth-celerybeat.conf
vendored
10
thirdparty/Supervisor/auth-celerybeat.conf
vendored
@ -1,10 +0,0 @@
|
|||||||
[program:auth-celerybeat]
|
|
||||||
command=celery -A alliance_auth beat
|
|
||||||
directory=/home/allianceserver/allianceauth
|
|
||||||
user=allianceserver
|
|
||||||
stdout_logfile=/home/allianceserver/allianceauth/log/beat.log
|
|
||||||
stderr_logfile=/home/allianceserver/allianceauth/log/beat.log
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=10
|
|
||||||
priority=999
|
|
13
thirdparty/Supervisor/auth-celeryd.conf
vendored
13
thirdparty/Supervisor/auth-celeryd.conf
vendored
@ -1,13 +0,0 @@
|
|||||||
[program:auth-celeryd]
|
|
||||||
command=celery -A alliance_auth worker
|
|
||||||
directory=/home/allianceserver/allianceauth
|
|
||||||
user=allianceserver
|
|
||||||
numprocs=1
|
|
||||||
stdout_logfile=/home/allianceserver/allianceauth/log/worker.log
|
|
||||||
stderr_logfile=/home/allianceserver/allianceauth/log/worker.log
|
|
||||||
autostart=true
|
|
||||||
autorestart=true
|
|
||||||
startsecs=10
|
|
||||||
stopwaitsecs = 600
|
|
||||||
killasgroup=true
|
|
||||||
priority=1000
|
|
28
thirdparty/Supervisor/auth.conf
vendored
Normal file
28
thirdparty/Supervisor/auth.conf
vendored
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
[program:celerybeat]
|
||||||
|
command=celery -A alliance_auth beat
|
||||||
|
directory=/home/allianceserver/allianceauth
|
||||||
|
user=allianceserver
|
||||||
|
stdout_logfile=/home/allianceserver/allianceauth/log/beat.log
|
||||||
|
stderr_logfile=/home/allianceserver/allianceauth/log/beat.log
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=10
|
||||||
|
priority=998
|
||||||
|
|
||||||
|
[program:celeryd]
|
||||||
|
command=celery -A alliance_auth worker
|
||||||
|
directory=/home/allianceserver/allianceauth
|
||||||
|
user=allianceserver
|
||||||
|
numprocs=1
|
||||||
|
stdout_logfile=/home/allianceserver/allianceauth/log/worker.log
|
||||||
|
stderr_logfile=/home/allianceserver/allianceauth/log/worker.log
|
||||||
|
autostart=true
|
||||||
|
autorestart=true
|
||||||
|
startsecs=10
|
||||||
|
stopwaitsecs = 600
|
||||||
|
killasgroup=true
|
||||||
|
priority=998
|
||||||
|
|
||||||
|
[group:auth]
|
||||||
|
programs=celerybeat,celeryd
|
||||||
|
priority=999
|
Loading…
x
Reference in New Issue
Block a user