From 3f0e8e0a243a6d2640e0f8356f5749f5939b4667 Mon Sep 17 00:00:00 2001 From: klongeiger Date: Sat, 22 Oct 2022 16:08:57 +0200 Subject: [PATCH] Generalizations Comments, changes to reflect common usage scenarios --- WordList.txt | 178 ++++++++++++++++++++++++++++++++++++++++++++ generateUserlist.py | 58 ++++++++++----- importUsers.py | 36 ++++++++- 3 files changed, 251 insertions(+), 21 deletions(-) create mode 100644 WordList.txt diff --git a/WordList.txt b/WordList.txt new file mode 100644 index 0000000..ec6d924 --- /dev/null +++ b/WordList.txt @@ -0,0 +1,178 @@ +Abend +Abendrot +Aldebaran +Allianz +Analyse +Angebot +Antwort +Aufgabe +Auftakt +Augenstern +Augenweide +Ausgleich +Bahnhof +Basis +Bedeutung +Begegnung +Beispiel +Beitrag +Beratung +Berufung +Bewegung +Blickfang +Butterblume +Borgelicht +Chance +Charakter +Chiffon +Denken +Dialog +Dutzend +Ebene +Einheit +Einigung +Energie +Farbe +Farbenfroh +Fernweh +Feuervogel +Firlefanz +Firmament +Forschung +Freiheit +Freude +Frieden +Fusion +Galerie +Garten +Gedanke +Gedanken +Gegenwart +Geist +Gestalt +Haltung +Heimat +Heimelig +Herbst +Herzlich +Himmel +Himmelsblau +Hoffnung +Insel +Interesse +Jahrzehnt +Jenseits +Joghurt +Jubel +Kamera +Karte +Kenntnis +Kleinod +Konzert +Kraft +Kultur +Kunst +Labsal +Lebenslust +Lehre +Libelle +Licht +Lichtblick +Lichtspiel +Liebe +Literatur +Lobhudeln +Luftikus +Luftschlange +Luftschloss +Medium +Meister +Mondschein +Morgenstern +Morgentau +Mummenschanz +Museum +Nachbar +Nacht +Naschkatze +Naseweis +Natur +Nordstern +Ofenwarm +Ohrenschmaus +Ohrwurm +Ordnung +Ostwind +Pflege +Plauderei +Prinzip +Purzelbaum +Pusteblume +Pustekuchen +Quasar +Quelle +Quitte +Rebell +Reform +Regenbogen +Richtung +Runde +Sammlung +Samtpfote +Sandkasten +Sandstein +Saumselig +Schatten +Schlummern +Schmollmund +Schutz +Seele +Sektlaune +Sommer +Sommerfrische +Sonne +Sperenzchen +Spiegel +Spielen +Sprache +Steckenpferd +Sternenzelt +Sternschnuppe +Sternstunde +Stimmung +Stubentiger +Szene +Tagtraum +Teilhabe +Tendenz +Theater +Tollpatsch +Tradition +Traum +Ukulele +Umwelt +Unsinn +Variante +Vertrauen +Vollmond +Vorfreude +Vorschlag +Waffel +Wahrheit +Wanderlust +Wildwasser +Wintertag +Wissen +Wundervoll +Wunschtraum +Xenon +Xenophil +Xylophon +Yacht +Yttrium +Yucca +Zeichen +Zeitlos +Zeitpunkt +Zeitraum +Zwielicht diff --git a/generateUserlist.py b/generateUserlist.py index 98c5a19..bad59ba 100644 --- a/generateUserlist.py +++ b/generateUserlist.py @@ -1,38 +1,62 @@ -# Generate import file +# Generate Userlist +# +# Rev 1.1, 10/22/22 +# - changed the -y (year of enrolement) parameter to a more general -s (suffix) +# - updated the usage message to include that change +# - added a lot of comments, especially regarding the default values +# - removed the unused fileinput import +# - changed the defaults to fit more common scenarios +# Rev 1.0, 10/20/22 +# - a simple script to generate CSV-files suitable as input +# for the importUsers script. +# - this script is rather specifically designed to accomodate +# the needs of our school's user naming scheme. Different +# naming schemes should be easy to implement though. +# - the script generates default passwords by picking three +# words from a file called WordList.txt living in the same +# directory as the script file. +# Options +# -d,--domain: domain name for the email address, this is required +# -g,--groups: comma-separated list of groups added to every user +# -s,--suffix: added to every username and the resulting filename +# in this release, the suffix is added to the last group name as well +# -f,--file: file of usernames, first and last name separated by comma import sys, getopt import csv import os,io -import fileinput import random def main(argv): wordList = open("WordList.txt").read().splitlines() - groups = "Schueler" - yoe = "00" - domainname="hd.waldorf.one" + # add some default values for the options + groups = "Users" # add default groups for all imports here + suffix = "" + domainname="example.com" filename = "names.csv" - + # next is a list of characters common in users actual names but unsuitable for login names + # for this script, those are specific to german names. Spaces get replaced with dashes. + char_map = {ord("ä"):"ae", ord("ü"):"ue", ord("ö"):"oe", ord("ß"):"ss", ord(" "):"-"} + try: - opts, args = getopt.getopt(argv,"hg:d:y:f:",["help","groups=","domain=","year=","file="]) + opts, args = getopt.getopt(argv,"hg:d:s:f:",["help","groups=","domain=","suffix=","file="]) except getopt.GetoptError: - print("Usage: importUsers.py -d [-g ] [-y -f ") + print("Usage: importUsers.py -d [-g ] [-s -f ") sys.exit(2) for opt, arg in opts: if opt == '-h': - print("Usage: importUsers.py -d [-g ] -y -f ") + print("Usage: importUsers.py -d [-g ] -s -f ") elif opt in ('-d', '--domain'): domainname = arg elif opt in ('-g', '--groups'): - groups = groups + "," + arg + groups = groups + "," + arg # as per importUsers specs, the groups are comma-separated elif opt in ('-f', '--file'): filename = arg - elif opt in ('-y', '--year'): - yoe = arg - groups = groups + ",schueler-hd-" + yoe + elif opt in ('-s', '--suffix'): + suffix = arg + groups = groups + suffix - outputFileName = "users_" + yoe + ".csv" - char_map = {ord("ä"):"ae", ord("ü"):"ue", ord("ö"):"oe", ord("ß"):"ss", ord(" "):"-"} + outputFileName = "users" + suffix + ".csv" with open(outputFileName,"w+",newline='') as outputFile: header = ["first_name","last_name","email_address","sis_username","user_groups","password"] writer = csv.DictWriter(outputFile,fieldnames=header,delimiter=";", quoting=csv.QUOTE_MINIMAL) @@ -40,9 +64,9 @@ def main(argv): users = open(filename,"r").read().splitlines() for user in users: first_name,last_name = tuple(user.split(",")) - username = first_name.casefold().translate(char_map) + "." + last_name.casefold().translate(char_map) + "-" + yoe + username = first_name.casefold().translate(char_map) + "." + last_name.casefold().translate(char_map) + suffix address = username + "@" + domainname - password = "-".join(random.sample(wordList,3)) + password = "-".join(random.sample(wordList,3)) # pick three words at ramdom, join them with a dash writer.writerow({"first_name":first_name,"last_name":last_name,"email_address":address,"sis_username":username,"user_groups":groups,"password":password}) if __name__ == "__main__": diff --git a/importUsers.py b/importUsers.py index 34c1ebf..b9b1be0 100755 --- a/importUsers.py +++ b/importUsers.py @@ -1,5 +1,9 @@ # Simple Cloudron bulk user import script # Uses the Cloudron REST API v1 +# Rev 1.4, 10/19/22, changes: +# - added local storage of access token including a limited means to check for validity +# - if mailboxes were supposed to be added and that failed, the tool will dump the missing users +# into a csv file formatted to import into the Cloudron Email management section. # Rev 1.3, 10/16/22, changes: # - added option to add passwords as part of the import # Please note that this method obviously discloses passwords to the admin, so @@ -27,9 +31,7 @@ # Douglas;Adams;douglas.adams@example.com;d.adams;staff,writers # ToDo: # - groups from the user_group field must already exist. If a specified group is not available, an error is displayed. -# Future revisions could ask whether missing groups should be created -# - 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 +# Future revisions could ask whether missing groups should be created. # - 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 @@ -38,9 +40,23 @@ import sys, getopt import getpass import requests import csv +import os +import pickle from json import loads +from datetime import datetime +from dateutil.relativedelta import relativedelta def requestAccessToken(domain, username=''): + # check for valid access token + if os.path.exists(".cat.pkl"): + with open(".cat.pkl","rb") as cloudronAccessTokenFile: + accessTokenDict = pickle.load(cloudronAccessTokenFile) + + terminationDate = datetime.now() + relativedelta(years=1) + if (accessTokenDict['domain'] == domain) and (accessTokenDict['date'] < terminationDate): + return accessTokenDict['token'] + + # current token is either invalid or for another domain, create a new one apiBasePath = 'https://' + domain + "/api/v1" if( username == '' ): username = input("Enter username for " + domain + ": ") @@ -49,7 +65,12 @@ def requestAccessToken(domain, username=''): 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'] + accessToken = a.json()['accessToken'] + accessTokenDict = {"domain":domain,"date":datetime.now(),"token":accessToken} + with open(".cat.pkl","wb+") as cloudronAccessTokenFile: + pickle.dump(accessTokenDict,cloudronAccessTokenFile) + + return accessToken else: print(f"Error requesting access token: {a.status_code}") sys.exit(2) @@ -72,6 +93,7 @@ def main(argv): sys.exit() elif opt in ('-f', '--file'): dataFilePath = arg + directory,dataFileName = os.path.split(dataFilePath) elif opt in ('-d', '--domain'): domain = arg elif opt in ('-u','--username'): @@ -135,6 +157,7 @@ def main(argv): # If a mailbox was to be added, do that now (we have the id already) if( addmailbox == True ): + missingMailboxesFilename = "mailbox_" + dataFileName payload = {"name":entry['sis_username'], "ownerId":curUserId, "ownerType":"user"} userDomain = entry['email_address'].split('@')[-1] mailUrl = apiBasePath + '/mail/'+userDomain+'/mailboxes?access_token='+accessToken @@ -143,6 +166,11 @@ def main(argv): print(f"Mailbox for user {displayName} created as {entry['email_address']}") else: print(f"Could not create mailbox for user {displayName}, error code {p.status_code}") + print(f"Dumping data to import file ") + + with open(missingMailboxesFilename,"a+",newline="") as mailboxFile: + writer = csv.writer(mailboxFile,delimiter=",") + writer.writerow([entry['sis_username'],userDomain,curUserId,"user"]) else: print(f"User {displayName} could not be created, statuscode {r.status_code}")