initial commit
commit
7711b9fcdd
|
|
@ -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:])
|
||||
Loading…
Reference in New Issue