initial commit

main
Georg Klein 2021-03-10 15:36:59 +01:00
commit 7711b9fcdd
2 changed files with 126 additions and 0 deletions

0
README.md Normal file
View File

126
importUsers.py Executable file
View File

@ -0,0 +1,126 @@
#!/usr/bin/python3
# Simple Cloudron bulk user import script
# Uses the cloudron REST API v1
# Rev 1.1, 03/07/21, changes:
# - added additional code and option -m to automatically create mailboxes on the appropriate domain.
# This should only be used if the mailaddress in the CSV-file belongs to the current cloudron (sub)domain
# CVS file requirements:
# - csv separator is ';' not ',' (which makes it a not-standard csv file, actually)
# - fieldnames [first_name, last_name, email_address, sis_username, person_id]
# - first three are self explanatory.
# Of these, only email_address is required by cloudron. the other fields must exist but can be empty
# - sis_username is turned into the optional username in cloudron, field must exist but can be empty
# - person_id follows a DATA-DATA-nn scheme, where only the nn part is used
# - example for a minimum CSV file:
# person_id;first_name;last_name;email_address;sis_username
# SOME-STUFF-01;Douglas;Adams;douglas.adams@example.com;d.adams
# ToDo:
# - group membership is hardcoded to our needs right now (s. FIX ME below), this is a Really Bad Idea (tm)
# - csv input format follows the AppleSchoolManager students.csv format, this should be more general
# see https://support.apple.com/guide/apple-school-manager/students-template-tes1bd5a3b1e/web for details
# - currently, a new access token is generated for every run of the script.
# This could be stored somewhere for consecutive runs, the standard expiry is a year
# - could use some sanity checks for input (low priority, if you want to sabotage yourself, go ahead)
import sys, getopt
import getpass
import requests
import csv
from json import loads
def requestAccessToken(domain, username=''):
apiBasePath = 'https://' + domain + "/api/v1"
if( username == '' ):
username = input("Enter username for " + domain + ": ")
password = getpass.getpass("Enter password for " + domain + ": ")
totpToken = input("Enter current totpToken: ")
a = requests.post(apiBasePath + '/cloudron/login', json={"username":username, "password":password, "totpToken":totpToken})
if( a.status_code == requests.codes.ok ):
return a.json()['accessToken']
else:
print("Error requesting access token: ", a.status_code)
sys.exit(2)
def main(argv):
domain = ''
username = ''
dataFilePath = './users.csv'
try:
opts, args = getopt.getopt(argv,"hf:d:u:m",["help","file=","domain=","username=","add-mailbox"])
except getopt.GetoptError:
print("importUsers.py -f <datafile> -d <domainname> -u <username>")
sys.exit(2)
for opt, arg in opts:
if opt == '-h':
print("importUsers.py -f <datafile> -d <domainname> -u <username>")
sys.exit()
elif opt in ('-f', '--file'):
dataFilePath = arg
elif opt in ('-d', '--domain'):
domain = arg
elif opt in ('-u','--username'):
username = arg
elif opt in ('-m','--add-mailbox'):
addmailbox = True
if( domain == '' ):
print("domainname must be provided, use the -d flag")
sys.exit(2)
apiBasePath = 'https://' + domain + "/api/v1"
# get current access Token
accessToken = requestAccessToken(domain, username)
# read existing groups from the cloudron and build an abbreviated dictionary of name and id
r = requests.get(apiBasePath + '/groups?access_token='+accessToken)
groupData = loads(r.text)['groups']
groups = {}
for g in groupData:
groups[g['name']] = g['id']
# read users from csv file and add to cloudron
with open(dataFilePath) as users:
reader = csv.DictReader(users, delimiter=';')
for entry in reader:
# fill payload object with data from the csv
displayName = entry['first_name'] + ' ' + entry['last_name']
requestUrl = apiBasePath + '/users?access_token='+accessToken
payload = {"email":entry['email_address'], "username":entry['sis_username'], "displayName":displayName, "password":""}
r = requests.post(requestUrl, json=payload)
if( r.status_code == requests.codes.created ):
print("User " + displayName + " succesfully created")
curUserId = r.json()['id']
userUrl = apiBasePath + '/users/'+curUserId+'/groups?access_token='+accessToken
# look up current user's group id and prepare the groups array for adding the user to the groups
# FIX ME: this section uses hardcoded information to calculate the appropriate group names for our naming scheme
classGroup = 'schueler-hd-' + entry['person_id'].split('-')[2].lower()
userGroups = {"groupIds":[groups['Schueler'],groups[classGroup]]}
p = requests.put(userUrl,userGroups)
if( p.status_code == requests.codes.no_content):
print("User " + displayName + " added to groups 'Schueler' & " + classGroup)
else:
print("Could not add user " + displayName + " to groups!")
# If a mailbox was to be added, do that now (we have the id already)
if( addmailbox == True ):
payload = {"name":entry['sis_username'], "ownerId":curUserId, "ownerType":"user"}
userDomain = entry['email_address'].split('@')[-1]
mailUrl = apiBasePath + '/mail/'+userDomain+'/mailboxes?access_token='+accessToken
p = requests.post(mailUrl, json=payload)
if( p.status_code == requests.codes.created ):
print("Mailbox for user " + displayName + " created as " + entry['email_address'])
else:
print("Could not create mailbox for user " + displayName + ", error code " + p.status_code)
else:
print('User ' + displayName + ' could not be created, statuscode ', r.status_code)
if __name__ == "__main__":
main(sys.argv[1:])