Compare commits
3 Commits
7711b9fcdd
...
e67c63372d
| Author | SHA1 | Date |
|---|---|---|
|
|
e67c63372d | |
|
|
dfb880a6dd | |
|
|
3da712347d |
|
|
@ -1,27 +1,32 @@
|
||||||
#!/usr/bin/python3
|
|
||||||
|
|
||||||
# Simple Cloudron bulk user import script
|
# Simple Cloudron bulk user import script
|
||||||
# Uses the cloudron REST API v1
|
# Uses the Cloudron REST API v1
|
||||||
|
# Rev 1.2, 03/019/21, changes:
|
||||||
|
# - removed the she-bang because it was interfering with alternate python paths. Use python importUsers.py to run
|
||||||
|
# - changed the CSV file format. The person_id field is gone, user_groups is in. This change allows flexibility
|
||||||
|
# on group membership (arbitrary number of groups)
|
||||||
|
# - got rid of the hardcoded values for group membership, use the new user_groups field
|
||||||
|
# - cleaned up the print statements to use formatted strings instead of awkward concatenations
|
||||||
# Rev 1.1, 03/07/21, changes:
|
# Rev 1.1, 03/07/21, changes:
|
||||||
# - added additional code and option -m to automatically create mailboxes on the appropriate domain.
|
# - 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
|
# This should only be used if the mailaddress in the CSV-file belongs to the current cloudron (sub)domain
|
||||||
# CVS file requirements:
|
# CVS file requirements:
|
||||||
# - csv separator is ';' not ',' (which makes it a not-standard csv file, actually)
|
# - csv separator is ';' not ',' (which makes it a not-standard csv file, actually)
|
||||||
# - fieldnames [first_name, last_name, email_address, sis_username, person_id]
|
# - fieldnames [first_name; last_name; email_address; sis_username; user_groups]
|
||||||
# - first three are self explanatory.
|
# - first three are self explanatory.
|
||||||
# Of these, only email_address is required by cloudron. the other fields must exist but can be empty
|
# 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
|
# - 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
|
# - user_groups is a comma-separated list of groups the user should be added to
|
||||||
# - example for a minimum CSV file:
|
# - example for a minimum CSV file:
|
||||||
# person_id;first_name;last_name;email_address;sis_username
|
# first_name;last_name;email_address;sis_username;user_groups
|
||||||
# SOME-STUFF-01;Douglas;Adams;douglas.adams@example.com;d.adams
|
# Douglas;Adams;douglas.adams@example.com;d.adams;staff,writers
|
||||||
# ToDo:
|
# ToDo:
|
||||||
# - group membership is hardcoded to our needs right now (s. FIX ME below), this is a Really Bad Idea (tm)
|
# - groups from the user_group field must already exist. If a specified group is not available, an error is displayed.
|
||||||
# - csv input format follows the AppleSchoolManager students.csv format, this should be more general
|
# Future revisions could ask whether missing groups should be created
|
||||||
# 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.
|
# - 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
|
# 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)
|
# - could use some sanity checks for input (low priority, if you want to sabotage yourself, go ahead)
|
||||||
|
# - totpTokens are always requested, even if the user hasn't configured token 2FA (they probably should)
|
||||||
|
# - Check, if the supplied email address is in the same domain as the Cloudron if the -m option is set
|
||||||
|
|
||||||
import sys, getopt
|
import sys, getopt
|
||||||
import getpass
|
import getpass
|
||||||
|
|
@ -40,13 +45,14 @@ def requestAccessToken(domain, username=''):
|
||||||
if( a.status_code == requests.codes.ok ):
|
if( a.status_code == requests.codes.ok ):
|
||||||
return a.json()['accessToken']
|
return a.json()['accessToken']
|
||||||
else:
|
else:
|
||||||
print("Error requesting access token: ", a.status_code)
|
print(f"Error requesting access token: {a.status_code}")
|
||||||
sys.exit(2)
|
sys.exit(2)
|
||||||
|
|
||||||
def main(argv):
|
def main(argv):
|
||||||
domain = ''
|
domain = ''
|
||||||
username = ''
|
username = ''
|
||||||
dataFilePath = './users.csv'
|
dataFilePath = './users.csv'
|
||||||
|
addmailbox = False
|
||||||
|
|
||||||
try:
|
try:
|
||||||
opts, args = getopt.getopt(argv,"hf:d:u:m",["help","file=","domain=","username=","add-mailbox"])
|
opts, args = getopt.getopt(argv,"hf:d:u:m",["help","file=","domain=","username=","add-mailbox"])
|
||||||
|
|
@ -94,19 +100,26 @@ def main(argv):
|
||||||
|
|
||||||
r = requests.post(requestUrl, json=payload)
|
r = requests.post(requestUrl, json=payload)
|
||||||
if( r.status_code == requests.codes.created ):
|
if( r.status_code == requests.codes.created ):
|
||||||
print("User " + displayName + " succesfully created")
|
print(f"User {displayName} succesfully created")
|
||||||
curUserId = r.json()['id']
|
curUserId = r.json()['id']
|
||||||
userUrl = apiBasePath + '/users/'+curUserId+'/groups?access_token='+accessToken
|
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
|
# 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
|
groupNames = entry['user_groups'].split(',')
|
||||||
classGroup = 'schueler-hd-' + entry['person_id'].split('-')[2].lower()
|
userGroupsIDs = []
|
||||||
userGroups = {"groupIds":[groups['Schueler'],groups[classGroup]]}
|
for curGroup in groupNames:
|
||||||
p = requests.put(userUrl,userGroups)
|
try:
|
||||||
|
userGroupsIDs.append(groups[curGroup])
|
||||||
|
except:
|
||||||
|
print(f"Group {curGroup} doesn't exist on this Cloudron.")
|
||||||
|
|
||||||
|
userGroups = {"groupIds":userGroupsIDs}
|
||||||
|
|
||||||
|
p = requests.put(userUrl,json=userGroups)
|
||||||
if( p.status_code == requests.codes.no_content):
|
if( p.status_code == requests.codes.no_content):
|
||||||
print("User " + displayName + " added to groups 'Schueler' & " + classGroup)
|
print(f"User {displayName} added to groups {groupNames}")
|
||||||
else:
|
else:
|
||||||
print("Could not add user " + displayName + " to groups!")
|
print(f"Could not add user {displayName} to groups, error code {p.status_code}")
|
||||||
|
|
||||||
# If a mailbox was to be added, do that now (we have the id already)
|
# If a mailbox was to be added, do that now (we have the id already)
|
||||||
if( addmailbox == True ):
|
if( addmailbox == True ):
|
||||||
|
|
@ -115,12 +128,12 @@ def main(argv):
|
||||||
mailUrl = apiBasePath + '/mail/'+userDomain+'/mailboxes?access_token='+accessToken
|
mailUrl = apiBasePath + '/mail/'+userDomain+'/mailboxes?access_token='+accessToken
|
||||||
p = requests.post(mailUrl, json=payload)
|
p = requests.post(mailUrl, json=payload)
|
||||||
if( p.status_code == requests.codes.created ):
|
if( p.status_code == requests.codes.created ):
|
||||||
print("Mailbox for user " + displayName + " created as " + entry['email_address'])
|
print(f"Mailbox for user {displayName} created as {entry['email_address']}")
|
||||||
else:
|
else:
|
||||||
print("Could not create mailbox for user " + displayName + ", error code " + p.status_code)
|
print(f"Could not create mailbox for user {displayName}, error code {p.status_code}")
|
||||||
|
|
||||||
else:
|
else:
|
||||||
print('User ' + displayName + ' could not be created, statuscode ', r.status_code)
|
print(f"User {displayName} could not be created, statuscode {r.status_code}")
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
main(sys.argv[1:])
|
main(sys.argv[1:])
|
||||||
Loading…
Reference in New Issue