discourse API with external package

This commit is contained in:
AaronKable 2020-09-11 17:19:54 +08:00
parent 6bcdc6052f
commit 051a48885c
5 changed files with 49 additions and 195 deletions

View File

@ -4,7 +4,7 @@ import re
from django.conf import settings from django.conf import settings
from django.core.cache import cache from django.core.cache import cache
from hashlib import md5 from hashlib import md5
from . import providers
logger = logging.getLogger(__name__) logger = logging.getLogger(__name__)
GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # default 2 hours GROUP_CACHE_MAX_AGE = getattr(settings, 'DISCOURSE_GROUP_CACHE_MAX_AGE', 2 * 60 * 60) # default 2 hours
@ -19,128 +19,8 @@ class DiscourseError(Exception):
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
ENDPOINTS = {
'groups': {
'list': {
'path': "/groups/search.json",
'method': 'get',
'args': {
'required': [],
'optional': [],
},
},
'create': {
'path': "/admin/groups",
'method': 'post',
'args': {
'required': ['name'],
'optional': ['visible'],
}
},
'add_user': {
'path': "/admin/groups/%s/members.json",
'method': 'put',
'args': {
'required': ['usernames'],
'optional': [],
},
},
'remove_user': {
'path': "/admin/groups/%s/members.json",
'method': 'delete',
'args': {
'required': ['username'],
'optional': [],
},
},
'delete': {
'path': "/admin/groups/%s.json",
'method': 'delete',
'args': {
'required': [],
'optional': [],
},
},
},
'users': {
'create': {
'path': "/users",
'method': 'post',
'args': {
'required': ['name', 'email', 'password', 'username'],
'optional': ['active'],
},
},
'update': {
'path': "/users/%s.json",
'method': 'put',
'args': {
'required': ['params'],
'optional': [],
}
},
'get': {
'path': "/users/%s.json",
'method': 'get',
'args': {
'required': [],
'optional': [],
},
},
'activate': {
'path': "/admin/users/%s/activate",
'method': 'put',
'args': {
'required': [],
'optional': [],
},
},
'set_email': {
'path': "/users/%s/preferences/email",
'method': 'put',
'args': {
'required': ['email'],
'optional': [],
},
},
'suspend': {
'path': "/admin/users/%s/suspend",
'method': 'put',
'args': {
'required': ['duration', 'reason'],
'optional': [],
},
},
'unsuspend': {
'path': "/admin/users/%s/unsuspend",
'method': 'put',
'args': {
'required': [],
'optional': [],
},
},
'logout': {
'path': "/admin/users/%s/log_out",
'method': 'post',
'args': {
'required': [],
'optional': [],
},
},
'external': {
'path': "/users/by-external/%s.json",
'method': 'get',
'args': {
'required': [],
'optional': [],
},
},
},
}
class DiscourseManager: class DiscourseManager:
def __init__(self): def __init__(self):
pass pass
@ -148,55 +28,14 @@ class DiscourseManager:
SUSPEND_DAYS = 99999 SUSPEND_DAYS = 99999
SUSPEND_REASON = "Disabled by auth." SUSPEND_REASON = "Disabled by auth."
@staticmethod
def __exc(endpoint, *args, **kwargs):
params = {
'api_key': settings.DISCOURSE_API_KEY,
'api_username': settings.DISCOURSE_API_USERNAME,
}
silent = kwargs.pop('silent', False)
if args:
endpoint['parsed_url'] = endpoint['path'] % args
else:
endpoint['parsed_url'] = 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 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))
r = getattr(requests, endpoint['method'])(settings.DISCOURSE_URL + endpoint['parsed_url'], headers=params,
json=data)
try:
if 'errors' in r.json() and not silent:
logger.error("Discourse execution failed.\nEndpoint: %s\nErrors: %s" % (endpoint, r.json()['errors']))
raise DiscourseError(endpoint, r.json()['errors'])
if 'success' in r.json():
if not r.json()['success'] and not silent:
raise DiscourseError(endpoint, None)
out = r.json()
except ValueError:
out = r.text
finally:
try:
r.raise_for_status()
except requests.exceptions.HTTPError as e:
raise DiscourseError(endpoint, e.response.status_code)
return out
@staticmethod @staticmethod
def _get_groups(): def _get_groups():
endpoint = ENDPOINTS['groups']['list'] data = providers.discourse.client.groups()
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 _create_group(name): def _create_group(name):
endpoint = ENDPOINTS['groups']['create'] return providers.discourse.client.create_group(name=name[:20], visible=True)['basic_group']
return DiscourseManager.__exc(endpoint, name=name[:20], visible=True)['basic_group']
@staticmethod @staticmethod
def _generate_cache_group_name_key(name): def _generate_cache_group_name_key(name):
@ -234,13 +73,11 @@ class DiscourseManager:
@staticmethod @staticmethod
def __add_user_to_group(g_id, username): def __add_user_to_group(g_id, username):
endpoint = ENDPOINTS['groups']['add_user'] providers.discourse.client.add_group_member(g_id, username)
DiscourseManager.__exc(endpoint, g_id, usernames=username)
@staticmethod @staticmethod
def __remove_user_from_group(g_id, username): def __remove_user_from_group(g_id, uid):
endpoint = ENDPOINTS['groups']['remove_user'] providers.discourse.client.delete_group_member(g_id, uid)
DiscourseManager.__exc(endpoint, g_id, username=username)
@staticmethod @staticmethod
def __generate_group_dict(names): def __generate_group_dict(names):
@ -252,39 +89,35 @@ class DiscourseManager:
@staticmethod @staticmethod
def __get_user_groups(username): def __get_user_groups(username):
data = DiscourseManager.__get_user(username) data = DiscourseManager.__get_user(username)
return [g['id'] for g in data['user']['groups'] if not g['automatic']] return [g['id'] for g in data['groups'] if not g['automatic']]
@staticmethod @staticmethod
def __user_name_to_id(name, silent=False): def __user_name_to_id(name, silent=False):
data = DiscourseManager.__get_user(name, silent=silent) data = DiscourseManager.__get_user(name)
return data['user']['id'] return data['user']['id']
@staticmethod @staticmethod
def __get_user(username, silent=False): def __get_user(username, silent=False):
endpoint = ENDPOINTS['users']['get'] return providers.discourse.client.user(username)
return DiscourseManager.__exc(endpoint, username, silent=silent)
@staticmethod @staticmethod
def __activate_user(username): def __activate_user(username):
endpoint = ENDPOINTS['users']['activate']
u_id = DiscourseManager.__user_name_to_id(username) u_id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, u_id) providers.discourse.client.activate(u_id)
@staticmethod @staticmethod
def __update_user(username, **kwargs): def __update_user(username, **kwargs):
endpoint = ENDPOINTS['users']['update']
u_id = DiscourseManager.__user_name_to_id(username) u_id = DiscourseManager.__user_name_to_id(username)
DiscourseManager.__exc(endpoint, u_id, params=kwargs) providers.discourse.client.update_user(endpoint, u_id, **kwargs)
@staticmethod @staticmethod
def __create_user(username, email, password): def __create_user(username, email, password):
endpoint = ENDPOINTS['users']['create'] providers.discourse.client.create_user(username, username, email, password)
DiscourseManager.__exc(endpoint, name=username, username=username, email=email, password=password, active=True)
@staticmethod @staticmethod
def __check_if_user_exists(username): def __check_if_user_exists(username):
try: try:
DiscourseManager.__user_name_to_id(username, silent=True) DiscourseManager.__user_name_to_id(username)
return True return True
except DiscourseError: except DiscourseError:
return False return False
@ -292,30 +125,26 @@ class DiscourseManager:
@staticmethod @staticmethod
def __suspend_user(username): def __suspend_user(username):
u_id = DiscourseManager.__user_name_to_id(username) u_id = DiscourseManager.__user_name_to_id(username)
endpoint = ENDPOINTS['users']['suspend'] return providers.discourse.client.suspend(u_id, DiscourseManager.SUSPEND_DAYS,
return DiscourseManager.__exc(endpoint, u_id, duration=DiscourseManager.SUSPEND_DAYS, DiscourseManager.SUSPEND_REASON)
reason=DiscourseManager.SUSPEND_REASON)
@staticmethod @staticmethod
def __unsuspend(username): def __unsuspend(username):
u_id = DiscourseManager.__user_name_to_id(username) u_id = DiscourseManager.__user_name_to_id(username)
endpoint = ENDPOINTS['users']['unsuspend'] return providers.discourse.client.unsuspend(u_id)
return DiscourseManager.__exc(endpoint, u_id)
@staticmethod @staticmethod
def __set_email(username, email): def __set_email(username, email):
endpoint = ENDPOINTS['users']['set_email'] return providers.discourse.client.update_email(username, email)
return DiscourseManager.__exc(endpoint, username, email=email)
@staticmethod @staticmethod
def __logout(u_id): def __logout(u_id):
endpoint = ENDPOINTS['users']['logout'] return providers.discourse.client.log_out(u_id)
return DiscourseManager.__exc(endpoint, u_id)
@staticmethod @staticmethod
def __get_user_by_external(u_id): def __get_user_by_external(u_id):
endpoint = ENDPOINTS['users']['external'] data = providers.discourse.client.user_by_external_id(u_id)
return DiscourseManager.__exc(endpoint, u_id) return data
@staticmethod @staticmethod
def __user_id_by_external_id(u_id): def __user_id_by_external_id(u_id):
@ -351,7 +180,9 @@ class DiscourseManager:
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'] discord_user = DiscourseManager.__get_user_by_external(user.pk)
username = discord_user['username']
uid = discord_user['id']
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 x not in inv_group_dict] rem_groups = [x for x in user_groups if x not in inv_group_dict]
@ -364,7 +195,7 @@ class DiscourseManager:
logger.info( logger.info(
"Updating discourse user %s groups: removing %s" % (username, rem_groups)) "Updating discourse user %s groups: removing %s" % (username, rem_groups))
for g in rem_groups: for g in rem_groups:
DiscourseManager.__remove_user_from_group(g, username) DiscourseManager.__remove_user_from_group(g, uid)
@staticmethod @staticmethod
def disable_user(user): def disable_user(user):

View File

@ -16,3 +16,4 @@ class DiscourseUser(models.Model):
permissions = ( permissions = (
("access_discourse", u"Can access the Discourse service"), ("access_discourse", u"Can access the Discourse service"),
) )

View File

@ -0,0 +1,19 @@
from pydiscourse import DiscourseClient
from django.conf import settings
class DiscourseAPIClient():
_client = None
def __init__(self):
pass
@property
def client(self):
if not self._client:
self._client = DiscourseClient(
settings.DISCOURSE_URL,
api_username=settings.DISCOURSE_API_USERNAME,
api_key=settings.DISCOURSE_API_KEY)
return self._client
discourse = DiscourseAPIClient()

View File

@ -47,7 +47,8 @@ class DiscourseTasks:
logger.debug("Updating discourse groups for user %s" % user) logger.debug("Updating discourse groups for user %s" % user)
try: try:
DiscourseManager.update_groups(user) DiscourseManager.update_groups(user)
except: except Exception as e:
logger.exception(e)
logger.warn("Discourse group sync failed for %s, retrying in 10 mins" % user) logger.warn("Discourse group sync failed for %s, retrying in 10 mins" % user)
raise self.retry(countdown=60 * 10) raise self.retry(countdown=60 * 10)
logger.debug("Updated user %s discourse groups." % user) logger.debug("Updated user %s discourse groups." % user)
@ -63,3 +64,4 @@ class DiscourseTasks:
def get_username(user): def get_username(user):
from .auth_hooks import DiscourseService from .auth_hooks import DiscourseService
return NameFormatter(DiscourseService(), user).format_name() return NameFormatter(DiscourseService(), user).format_name()

View File

@ -31,6 +31,7 @@ install_requires = [
'openfire-restapi', 'openfire-restapi',
'sleekxmpp', 'sleekxmpp',
'pydiscourse',
'django-esi>=1.5,<3.0' 'django-esi>=1.5,<3.0'
] ]