import io import logging import base64 import hmac import hashlib import urllib.parse import requests from django.conf import settings logger = logging.getLogger(__name__) def sync_user_to_discourse(external_id, username, email, name, bio): assert external_id, 'external_id is required' assert username, 'username is required' assert email, 'email is required' logger.info(f'sync user to discourse: {external_id} {username}') sso_params = { 'external_id': external_id, 'username': username, 'email': email, 'name': name or '', 'bio': bio or '', } secret = settings.DISCOURSE_SSO_TOKEN api_key = settings.DISCOURSE_API_KEY url = settings.DISCOURSE_SSO_SYNC_ENDPOINT sso_payload = base64.encodebytes(urllib.parse.urlencode(sso_params).encode('utf-8')) sig = hmac.new( key=secret.encode('utf-8'), msg=sso_payload, digestmod=hashlib.sha256).hexdigest() try: resp = requests.post( url, data={ 'sso': sso_payload, 'sig': sig }, headers={ 'Api-Key': api_key, 'Api-Username': 'system' }, timeout=2 ) if resp.status_code != 200: logger.error( f'failed to sync user to discourse, status_code: {resp.status_code}, content: {resp.text}' ) return False except Exception as e: logger.error(f'failed to sync user to discourse: {e}') return False return True def grant_badge_to_users(usernames, badge_id, replace_badge_owners=False, grant_existing_holders=False): assert 0 < len(usernames) < 10000, 'usernames length must between 1 and 10000' usernames_file = io.BytesIO('\n'.join(usernames).encode('utf8')) try: resp = requests.post( f'{settings.DISCOURSE_ENDPOINT_PREFIX}/admin/badges/award/{badge_id}', data={ 'grant_existing_holders': grant_existing_holders, 'replace_badge_owners': replace_badge_owners, }, files={ 'file': ('name.csv', usernames_file), }, headers={ 'Api-Key': settings.DISCOURSE_API_KEY, 'Api-Username': 'system', 'accept': 'application/json' }, timeout=3 ) if resp.status_code != 200: logger.error( f'failed to grant badge({badge_id}) to users({usernames}), ' f'status_code: {resp.status_code}, content: {resp.text}' ) return False except Exception as e: logger.error( f'failed to grant badge({badge_id}) to users({usernames}),' f'error: {e}' ) return False return True def revoke_user_badge(user_badge_id): try: resp = requests.delete( f'{settings.DISCOURSE_ENDPOINT_PREFIX}/user_badges/{user_badge_id}', headers={ 'Api-Key': settings.DISCOURSE_API_KEY, 'Api-Username': 'system', 'accept': 'application/json' }, timeout=3 ) if resp.status_code != 200: logger.error( f'failed to revoke user badge: {user_badge_id}, ' f'status_code: {resp.status_code}, content: {resp.text}' ) return False except Exception as e: logger.error( f'failed to revoke user badge: {user_badge_id}, ' f'error: {e}' ) return False return True def set_flair_group_for_user(username, flair_group_id): try: resp = requests.put( f'{settings.DISCOURSE_ENDPOINT_PREFIX}/u/{username}', data={ 'flair_group_id': flair_group_id }, headers={ 'Api-Key': settings.DISCOURSE_API_KEY, 'Api-Username': 'system', 'accept': 'application/json' }, timeout=3 ) if resp.status_code != 200: logger.error( f'failed to set flair group: {flair_group_id} for user: {username}, ' f'status_code: {resp.status_code}, content: {resp.text}' ) return False elif flair_group_id and resp.json()['user']['flair_group_id'] != flair_group_id: logger.error( f'failed to set flair group: {flair_group_id} for user: {username}, ' f'even response success, but the user flair group still be {resp.json()["user"]["flair_group_id"]}' ) return False except Exception as e: logger.error( f'failed to set flair group: {flair_group_id} for user: {username}, ' f'error: {e}' ) return False return True def add_users_to_group(group_id, usernames, notify_users=False, ignore_422_error=True): # return success if anyone was added to the group, # otherwise return http_422_error if all users are already in the group try: resp = requests.put( f'{settings.DISCOURSE_ENDPOINT_PREFIX}/groups/{group_id}/members.json', data={ 'usernames': ','.join(usernames), 'emails': '', 'notify_users': notify_users }, headers={ 'Api-Key': settings.DISCOURSE_API_KEY, 'Api-Username': 'system', 'accept': 'application/json' }, timeout=3 ) if resp.status_code != 200 and not (resp.status_code == 422 and ignore_422_error): logger.error( f'failed to add user: {usernames} to group: {group_id}, ' f'status_code: {resp.status_code}, content: {resp.text}' ) return False except Exception as e: logger.error( f'failed to add user: {usernames} to group: {group_id}, ' f'error: {e}' ) return False return True def remove_user_from_group(group_id, user_id): # always return success even if user already removed from the group try: resp = requests.delete( f'{settings.DISCOURSE_ENDPOINT_PREFIX}/groups/{group_id}/members.json', data={ 'user_id': user_id }, headers={ 'Api-Key': settings.DISCOURSE_API_KEY, 'Api-Username': 'system', 'accept': 'application/json' }, timeout=3 ) if resp.status_code != 200: logger.error( f'failed to remove user: {user_id} from group: {group_id}, ' f'status_code: {resp.status_code}, content: {resp.text}' ) return False except Exception as e: logger.error( f'failed to remove user: {user_id} from group: {group_id}, ' f'error: {e}' ) return False return True def send_private_message(usernames, title, raw_message): try: resp = requests.post( f'{settings.DISCOURSE_ENDPOINT_PREFIX}/posts.json', json={ 'title': title, 'raw': raw_message, 'target_recipients': ','.join(usernames), 'archetype': 'private_message' }, headers={ 'Api-Key': settings.DISCOURSE_API_KEY, 'Api-Username': 'system', 'Content-Type': 'application/json' }, timeout=5 ) if resp.status_code != 200: logger.error( f'failed to send private message in discourse, reciver: {usernames}, ' f'title: {title}, status_code: {resp.status_code}, content: {resp.text}' ) return False except Exception as e: logger.error( f'failed to send private message in discourse, reciver: {usernames}, ' f'title: {title}, error: {e}' ) return False return True