You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

332 lines
11 KiB

from log import logger
import time
import secrets
import subprocess
import requests
from uuid import uuid4
from flask import abort
HTTP_NOTFOUND = 404
random_email = lambda: 'spaceport-' + str(uuid4()).split('-')[0] + '@protospace.ca'
def set_wiki_password(username, password):
# sets a user's wiki password
# creates the account if it doesn't exist
if not secrets.WIKI_MAINTENANCE:
logger.error('Wiki setting not configured, aborting')
abort(400)
if not username:
logger.error('Empty username, aborting')
abort(400)
logger.info('Setting wiki password for: ' + username)
if not password:
logger.error('Empty password, aborting')
abort(400)
script = secrets.WIKI_MAINTENANCE + '/createAndPromote.php'
result = subprocess.run(['php', script, '--force', username, password],
shell=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
output = result.stdout or result.stderr
output = output.strip()
logger.info('Output: ' + output)
if result.stderr:
abort(400)
def discourse_api_get(url, params={}):
headers = {
'Api-Key': secrets.DISCOURSE_API_KEY,
'Api-Username': secrets.DISCOURSE_API_USER,
}
response = requests.get(url, headers=headers, params=params, timeout=10)
logger.debug('Response: %s %s', response.status_code, response.text)
response.raise_for_status()
return response
def discourse_api_put(url, data={}):
headers = {
'Api-Key': secrets.DISCOURSE_API_KEY,
'Api-Username': secrets.DISCOURSE_API_USER,
}
response = requests.put(url, headers=headers, data=data, timeout=10)
logger.debug('Response: %s %s', response.status_code, response.text)
response.raise_for_status()
return response
def discourse_api_post(url, data={}):
headers = {
'Api-Key': secrets.DISCOURSE_API_KEY,
'Api-Username': secrets.DISCOURSE_API_USER,
}
response = requests.post(url, headers=headers, data=data, timeout=10)
logger.debug('Response: %s %s', response.status_code, response.text)
response.raise_for_status()
return response
def discourse_api_delete(url, data={}):
headers = {
'Api-Key': secrets.DISCOURSE_API_KEY,
'Api-Username': secrets.DISCOURSE_API_USER,
}
response = requests.delete(url, headers=headers, data=data, timeout=10)
logger.debug('Response: %s %s', response.status_code, response.text)
response.raise_for_status()
return response
def discourse_rails_script(script):
result = subprocess.run(['docker', 'exec', '-i', secrets.DISCOURSE_CONTAINER, 'rails', 'runner', script],
shell=False, text=True, stdout=subprocess.PIPE, stderr=subprocess.PIPE, timeout=60)
output = result.stdout or result.stderr
output = output.strip() or 'No complaints'
return result, output
def get_discourse_group_id(group_name):
logger.info('Getting the ID of group %s', group_name)
url = 'https://forum.protospace.ca/groups/{}.json'.format(group_name)
response = discourse_api_get(url)
response = response.json()
return response['group']['id']
def get_discourse_usernames():
usernames = []
response = discourse_api_get('https://forum.protospace.ca/groups/trust_level_0/members.json?limit=1000')
response = response.json()
for user in response['members']:
usernames.append(user['username'])
if len(usernames) == 1000:
logger.error('Hit username limit, aborting!')
abort(400)
return usernames
def translate_usernames(portal_usernames, discourse_usernames):
# the case of portal and discourse usernames might not match
# this causes a problem if someone creates a discourse user
# as John.Smith and later sets up a portal account as john.smith
#
# solution: look for usernames in discourse with the same letters,
# and then convert to the discourse version when using the API
result = []
for pu in portal_usernames:
for du in discourse_usernames:
if pu.lower() == du.lower():
result.append(du)
break
else: # for
result.append(pu)
return result
def set_discourse_password(username, password, first_name, email):
# sets a user's discourse password
# creates the account if it doesn't exist
# things to test:
# - user changes Spaceport password
# - user changes Spaceport password to same
# - new Spaceport signup
# - existing Discourse user Spaceport signup
# - existing Discourse user Spaceport signup with same email
# note: Spaceport emails are unconfirmed!!
if not secrets.DISCOURSE_CONTAINER or not secrets.DISCOURSE_API_KEY or not secrets.DISCOURSE_API_USER:
logger.error('Discourse setting not configured, aborting')
abort(400)
if not username:
logger.error('Empty username, aborting')
abort(400)
if not password:
logger.error('Empty password, aborting')
abort(400)
if not first_name:
logger.error('Empty first_name, aborting')
abort(400)
if not email:
logger.error('Empty email, aborting')
abort(400)
discourse_usernames = get_discourse_usernames()
username = translate_usernames([username], discourse_usernames)[0]
logger.info('Checking Discourse for existing email: ' + email)
params = {
'filter': email,
'show_emails': 'true',
}
response = discourse_api_get('https://forum.protospace.ca/admin/users/list/active.json', params)
response = response.json()
for user in response:
if user['email'].lower() == email.lower():
if user['username'] == username:
logger.info('Username match, skipping')
continue
new_email = random_email()
logger.info('Email found on different user %s, changing to: %s', user['username'], new_email)
script = 'UserEmail.find_by(email: "{}").update!(email: "{}")'.format(email, new_email)
result, output = discourse_rails_script(script)
logger.info('Confirming email change...')
response = discourse_api_get('https://forum.protospace.ca/admin/users/list/active.json', params)
if len(response.json()):
logger.error('Email change failed, aborting')
abort(400)
user_exists = username in discourse_usernames
if not user_exists:
logger.info('Creating Discourse user for: ' + username)
data = {
'name': first_name,
'username': username,
'password': password,
'email': email,
'active': True,
'approved': True,
'user_fields[10]': 'Spaceport auth',
'user_fields[11]': 'other',
}
response = discourse_api_post('https://forum.protospace.ca/users.json', data)
response = response.json()
logger.info('Response: %s', response)
logger.info('Skipping set password')
return True
else:
logger.info('User exists, setting Discourse password for: ' + username)
script = 'User.find_by(username: "{}").update!(password: "{}")'.format(username, password)
result, output = discourse_rails_script(script)
if 'Password is the same' in result.stderr:
logger.info('Output: Password is the same as your current password. (ActiveRecord::RecordInvalid)')
return True
else:
logger.info('Output: ' + output)
if result.stderr:
abort(400)
def add_discourse_group_members(group_name, usernames):
if not group_name:
logger.error('Empty group_name, aborting')
abort(400)
if not usernames:
logger.error('Empty usernames, aborting')
abort(400)
discourse_usernames = get_discourse_usernames()
usernames = translate_usernames(usernames, discourse_usernames)
usernames = set(usernames)
group_id = get_discourse_group_id(group_name)
logger.info('Filtering out usernames not on Discourse...')
discourse_usernames = set(discourse_usernames)
usernames = usernames & discourse_usernames
logger.info('Filtering out usernames that are already group members...')
url = 'https://forum.protospace.ca/groups/{}/members.json?limit=1000'.format(group_name)
response = discourse_api_get(url)
response = response.json()
member_usernames = set([m['username'] for m in response['members']])
usernames = usernames - member_usernames
usernames = list(usernames)
if not len(usernames):
logger.info('Skipping, no one left to add')
return True
logger.info('Adding %s remaining usernames to the group...', len(usernames))
url = 'https://forum.protospace.ca/groups/{}/members.json'.format(group_id)
data = {
'usernames': ','.join(usernames)
}
discourse_api_put(url, data)
return True
def remove_discourse_group_members(group_name, usernames):
if not group_name:
logger.error('Empty group_name, aborting')
abort(400)
if not usernames:
logger.error('Empty usernames, aborting')
abort(400)
discourse_usernames = get_discourse_usernames()
usernames = translate_usernames(usernames, discourse_usernames)
usernames = set(usernames)
group_id = get_discourse_group_id(group_name)
logger.info('Filtering out usernames not on Discourse...')
discourse_usernames = set(discourse_usernames)
usernames = usernames & discourse_usernames
usernames = list(usernames)
if not len(usernames):
logger.info('Skipping, no one left to remove')
return True
logger.info('Removing %s remaining usernames from the group...', len(usernames))
url = 'https://forum.protospace.ca/groups/{}/members.json'.format(group_id)
data = {
'usernames': ','.join(usernames)
}
discourse_api_delete(url, data)
return True
def change_discourse_username(username, new_username):
if not username:
logger.error('Empty username, aborting')
abort(400)
if not new_username:
logger.error('Empty new_username, aborting')
abort(400)
logger.info('Changing username %s to %s...', username, new_username)
url = 'https://forum.protospace.ca/users/{}/preferences/username'.format(username)
data = {
'new_username': new_username,
}
discourse_api_put(url, data)
return True
if __name__ == '__main__':
#set_wiki_password('tanner.collin', 'protospace1')
set_discourse_password('test8a', 'protospace1', 'testie', 'test8@example.com')
#for u in get_discourse_usernames():
# print(u)
#pass