From c15b0dd4bfd9c4f36199fefe64aa8435a878adc5 Mon Sep 17 00:00:00 2001
From: Philip Gaber <gaber@b1-systems.de>
Date: Tue, 12 Nov 2024 12:11:10 +0100
Subject: [PATCH] fix: Consistent type checks for cli arguments and ENV
 variables

---
 lib/argparse_types.py       |  11 +++
 lib/import_user.py          |  12 +--
 lib/random_user.py          |  19 +----
 lib/ucs.py                  |  36 +++-----
 user_import_udm_rest_api.py | 158 +++++++++++++++++++++++++++++++-----
 5 files changed, 167 insertions(+), 69 deletions(-)
 create mode 100644 lib/argparse_types.py

diff --git a/lib/argparse_types.py b/lib/argparse_types.py
new file mode 100644
index 0000000..ad2fced
--- /dev/null
+++ b/lib/argparse_types.py
@@ -0,0 +1,11 @@
+from configargparse import ArgumentTypeError
+
+def opt2bool(opt):
+    if isinstance(opt, bool):
+        return opt
+    elif opt.lower() in ['true', 'yes', 'ok', '1']:
+        return True
+    elif opt.lower() in ['false', 'no', 'nok', '0']:
+        return False
+    else:
+        raise ArgumentTypeError(f"Cannot convert {opt} into a boolean value.")
diff --git a/lib/import_user.py b/lib/import_user.py
index 6abbb55..9ca95ec 100644
--- a/lib/import_user.py
+++ b/lib/import_user.py
@@ -87,21 +87,11 @@ class ImportUser:
             if use_images:
                 person["jpegPhoto"] = self.__get_image()
             callback(person)
-            if self.__option2bool(create_admin_accounts):
+            if create_admin_accounts:
                 person['username'] = person['username']+'-admin'
                 person['is_admin'] = True
                 callback(person)
 
-    def __option2bool(self, string):
-        if isinstance(string, (bool)):
-            return string
-        elif string.lower() in ['true', 'yes', 'ok']:
-            return True
-        elif string.lower() in ['false', 'no', 'nok']:
-            return False
-        else:
-            sys.exit(f"Cannot convert {string} into a boolean value.")
-
     def __get_image(self):
         if (not hasattr(self, "input_filelist_img_list")):
             self.input_filelist_img_list= []
diff --git a/lib/random_user.py b/lib/random_user.py
index 80ce0d7..c4e478a 100644
--- a/lib/random_user.py
+++ b/lib/random_user.py
@@ -7,13 +7,12 @@ import glob
 import base64
 import random
 import string
-import sys
 
 class RandomUser:
 
-    def __init__(self, callback, create_admins = False, amount = 100, password_reset_mail = 'not_provided@opendesk.internal', randomize='True'):
+    def __init__(self, callback, create_admin_accounts = False, amount = 100, password_reset_mail = 'not_provided@opendesk.internal', randomize_username=True):
         self.usercounter = 0
-        self.randomize=randomize
+        self.randomize_username=randomize_username
         self.input_dir_imgs_base = "./data/images_"
         self.input_files_list = {
             "firstname": "./data/firstname_gender.tsv",
@@ -42,21 +41,11 @@ class RandomUser:
             person["email"] = password_reset_mail
             person['is_admin'] = False
             callback(person)
-            if self.__option2bool(create_admins):
+            if create_admin_accounts:
                 person['username'] = self.__get_username(person["firstname"], person["lastname"], admin=True)
                 person['is_admin'] = True
                 callback(person)
 
-    def __option2bool(self, string):
-        if isinstance(string, (bool)):
-            return string
-        elif string.lower() in ['true', 'yes', 'ok']:
-            return True
-        elif string.lower() in ['false', 'no', 'nok']:
-            return False
-        else:
-            sys.exit(f"Cannot convert {string} into a boolean value.")
-
     def __get_firstname_and_gender(self):
         to_split_result = self.__get_random_list_entry("firstname")
         return to_split_result.split('\t')
@@ -69,7 +58,7 @@ class RandomUser:
         return random.choice(self.lists[category])
 
     def __get_username(self, firstname, lastname, admin=False):
-        if self.randomize == 'True':
+        if self.randomize_username:
             username = unicodedata.normalize('NFKD', firstname+"."+lastname).encode('ascii', 'ignore')
             if admin:
                 return username.decode().lower()+"-admin"
diff --git a/lib/ucs.py b/lib/ucs.py
index fe4f221..37d369b 100644
--- a/lib/ucs.py
+++ b/lib/ucs.py
@@ -37,11 +37,11 @@ class Ucs:
         self.groups_available = []
         self.existing_maildomains = []
         self.existing_oxcontexts = []
-        if (self.__option2bool(self.options_object.create_maildomains)):
+        if (self.options_object.create_maildomains):
             for maildomain_object in self.__get_object_list('mail', 'domain'):
                 self.existing_maildomains.append(maildomain_object['id'])
             logging.debug(f"Pre-existing maildomains: {', '.join(self.existing_maildomains)}")
-        if (self.__option2bool(self.options_object.create_oxcontexts)):
+        if (self.options_object.create_oxcontexts):
             for oxcontext_object in self.__get_object_list('oxmail', 'oxcontext'):
                 self.existing_oxcontexts.append(oxcontext_object['id'])
             logging.debug(f"Pre-existing OX contexts: {', '.join(self.existing_oxcontexts)}")
@@ -145,16 +145,6 @@ class Ucs:
         self.create_count['groups'] += 1
         logging.debug(f"{group_name}: {response}")
 
-    def __option2bool(self, string):
-        if isinstance(string, (bool)):
-            return string
-        elif string.lower() in ['true', 'yes', 'ok']:
-            return True
-        elif string.lower() in ['false', 'no', 'nok']:
-            return False
-        else:
-            sys.exit(f"Cannot convert {string} into a boolean value.")
-
     def update_user(self, current_json, person):
         # Domain Admin & Domain Users are set as primaryGroups
         # The managed-by-attribute-* groups are automanaged anyway.
@@ -184,15 +174,15 @@ class Ucs:
             groups = []
         user_json = {
             "properties": {
-                "isOxUser": (not person['is_admin'] and not self.__option2bool(self.options_object.component_disable_groupware)),
-                "opendeskFileshareEnabled": (not person['is_admin'] and not self.__option2bool(self.options_object.component_disable_fileshare)),
-                "opendeskProjectmanagementEnabled": (not person['is_admin'] and not self.__option2bool(self.options_object.component_disable_projectmanagement)),
-                "opendeskKnowledgemanagementEnabled": (not person['is_admin'] and not self.__option2bool(self.options_object.component_disable_knowledgemanagement)),
-                "opendeskLivecollaborationEnabled": (not person['is_admin'] and not self.__option2bool(self.options_object.component_disable_livecollaboration)),
-                "opendeskVideoconferenceEnabled": (not person['is_admin'] and not self.__option2bool(self.options_object.component_disable_videoconference)),
-                "opendeskFileshareAdmin": self.__option2bool(self.options_object.admin_enable_fileshare),
-                "opendeskProjectmanagementAdmin": self.__option2bool(self.options_object.admin_enable_projectmanagement),
-                "opendeskKnowledgemanagementAdmin": self.__option2bool(self.options_object.admin_enable_knowledgemanagement),
+                "isOxUser": (not person['is_admin'] and not self.options_object.component_disable_groupware),
+                "opendeskFileshareEnabled": (not person['is_admin'] and not self.options_object.component_disable_fileshare),
+                "opendeskProjectmanagementEnabled": (not person['is_admin'] and not self.options_object.component_disable_projectmanagement),
+                "opendeskKnowledgemanagementEnabled": (not person['is_admin'] and not self.options_object.component_disable_knowledgemanagement),
+                "opendeskLivecollaborationEnabled": (not person['is_admin'] and not self.options_object.component_disable_livecollaboration),
+                "opendeskVideoconferenceEnabled": (not person['is_admin'] and not self.options_object.component_disable_videoconference),
+                "opendeskFileshareAdmin": self.options_object.admin_enable_fileshare,
+                "opendeskProjectmanagementAdmin": self.options_object.admin_enable_projectmanagement,
+                "opendeskKnowledgemanagementAdmin": self.options_object.admin_enable_knowledgemanagement,
                 "mailPrimaryAddress": person['username']+"@"+self.maildomain if not 'mailPrimaryAddress' in person or not isinstance(person['mailPrimaryAddress'], str) else person['mailPrimaryAddress'],
                 "PasswordRecoveryEmail": person['email'],
                 "oxContext": int(person['oxContext'] if 'oxContext' in person and not pd.isna(person['oxContext']) else self.options_object.default_oxcontext),
@@ -214,7 +204,7 @@ class Ucs:
                 logging.debug(f"attribute {key} not supported, skipping.")
                 del user_json['properties'][key]
 
-        if (self.__option2bool(self.options_object.create_maildomains)):
+        if (self.options_object.create_maildomains):
             users_maildomain = user_json['properties']['mailPrimaryAddress'].split('@')[-1]
             if not users_maildomain in self.existing_maildomains:
                 logging.info(f"Creating maildomain: {users_maildomain}")
@@ -232,7 +222,7 @@ class Ucs:
                 self.create_count['maildomains'] += 1
                 self.existing_maildomains.append(users_maildomain)
 
-        if (self.__option2bool(self.options_object.create_oxcontexts)):
+        if (self.options_object.create_oxcontexts):
             oxcontext = user_json['properties']['oxContext']
             if not str(oxcontext) in self.existing_oxcontexts:
                 logging.info(f"Creating OX context: {oxcontext}")
diff --git a/user_import_udm_rest_api.py b/user_import_udm_rest_api.py
index c49e27d..7c49c1e 100755
--- a/user_import_udm_rest_api.py
+++ b/user_import_udm_rest_api.py
@@ -10,6 +10,7 @@ import configargparse
 
 from pathlib import Path
 
+from lib.argparse_types import opt2bool
 from lib.ucs import Ucs
 from lib.random_user import RandomUser
 from lib.import_user import ImportUser
@@ -28,35 +29,144 @@ non_reconcile_groups = [
 ]
 
 p = configargparse.ArgParser()
-#p.add('--admin_enable_chat', env_var='ADMIN_ENABLE_CHAT', default=False, help='Optional: Set to "True" if users should get functional admin permissions for chat component - Requires openDesk Enterprise.')
-p.add('--admin_enable_fileshare', env_var='ADMIN_ENABLE_FILESHARE', default=False, help='Optional: Set to "True" if users should get functional admin permissions for fileshare component.')
-p.add('--admin_enable_knowledgemanagement', env_var='ADMIN_ENABLE_KNOWLEDGEMANAGEMENT', default=False, help='Optional: Set to "True" if users should get functional admin permissions for knowledgemanagement component.')
-p.add('--admin_enable_projectmanagement', env_var='ADMIN_ENABLE_PROJECTMANAGEMENT', default=False, help='Optional: Set to "True" if users should get functional admin permissions for projectmanagement component.')
-p.add('--component_disable_fileshare', env_var='COMPONENT_DISABLE_FILESHARE', default=False, help='Optional: Set to "True" if users should not get the flag for fileshare access.')
-p.add('--component_disable_groupware', env_var='COMPONENT_DISABLE_GROUPWARE', default=False, help='Optional: Set to "True" if users should not get the flag for groupware access.')
-p.add('--component_disable_knowledgemanagement', env_var='COMPONENT_DISABLE_KNOWLEDGEMANAGEMENT', default=False, help='Optional: Set to "True" if users should not get the flag for knowledgemanagement access.')
-p.add('--component_disable_livecollaboration', env_var='COMPONENT_DISABLE_LIVECOLLABORATION', default=False, help='Optional: Set to "True" if users should not get the flag for livecollaboration access.')
-p.add('--component_disable_projectmanagement', env_var='COMPONENT_DISABLE_PROJECTMANAGEMENT', default=False, help='Optional: Set to "True" if users should not get the flag for projectmanagement access.')
-p.add('--component_disable_videoconference', env_var='COMPONENT_DISABLE_VIDEOCONFERENCE', default=False, help='Optional: Set to "True" if users should not get the flag for videoconference access.')
-p.add('--create_admin_accounts', env_var='CREATE_ADMIN_ACCOUNTS', default=False, help='Optional: Set to "True" if each user also should get an additional "<username>-admin" account')
-p.add('--create_maildomains', env_var='CREATE_MAILDOMAINS', default=False, help='Optional: Set to "True" to get non existing mail domains auto-created. Only relevant when importing a files in which mailPrimaryAddresses are provided that differ from the default (mail)domain.')
-p.add('--create_oxcontexts', env_var='CREATE_OXCONTEXT', default=False, help='Optional: Set to "True" to get non-existing OX contexts auto-created.')
-p.add('--default_oxcontext', env_var='DEFAULT_OXCONTEXT', default=1, help='Optional: Set the default OX context ID to use when creating users.')
 p.add('--import_domain', env_var='IMPORT_DOMAIN', required=True, help='The domain name of your openDesk instance - omit the "portal." or other service specific hostnames.')
 p.add('--import_filename', env_var='IMPORT_FILENAME', required=False, default=None, help='The filename containing the user account details for the import - see template.ods for reference. If filename is not provided or related file is not found random users will be imported.')
-p.add('--import_use_images', env_var='IMPORT_USE_IMAGES', default=False, help='Optional: Set to "True" if each user should be uploaded with a random profile picture when "import_filename" was set.')
 p.add('--import_maildomain', env_var='IMPORT_MAILDOMAIN', required=False, help='Optional: If you are using a different maildomain please specify it, otherwise `IMPORT_DOMAIN` is used.')
-p.add('--import_random_amount', env_var='IMPORT_RANDOM_AMOUNT', default=10, help='The number of random accounts to import if the "import_filename" was not set or found.')
-p.add('--import_random_usernames', env_var='IMPORT_RANDOM_USERNAMES', default='True', help='If set to "False" the imported usernames of the imported follow the format user.N and admin.N.')
 p.add('--loglevel', env_var='LOGLEVEL', default='INFO', help='Set the loglevel: DEBUG, INFO, WARNING, ERROR, CRITICAL. Default: WARNING')
 p.add('--logpath', env_var='LOGPATH', default='./logs', help='Path where the script write its logfile to. Default: ./logs')
 p.add('--output_accounts_filename', env_var='OUTPUT_ACCOUNTS_FILENAME', required=False, default=None, help='The filename to write the created accounts (username<tab>password) into, appends if file exists). If none is provided the default name will be "users-<import_domain>-<timestamp>.txt"')
 p.add('--password_recovery_email', env_var='PASSWORD_RECOVERY_EMAIL', help='Optional: When creating random accounts this password recovery email is used.')
 p.add('--reconcile_groups', env_var='RECONCILE_GROUPS', default=False, help='Optional: Set to "True" if groups on the users should be reconciled based on the input file. Will remove all groups from the user not defined in sheet except for the standard groups: '+', '.join(non_reconcile_groups))
 p.add('--set_default_password', env_var='SET_DEFAULT_PASSWORD', default='', help='Optional: When set the given password is used on the newly created accounts, otherwise a random one will be created.')
-p.add('--trigger_invitation_mail', env_var='TRIGGER_INVITATION_MAIL', help='Optional: Set to "True" if you want invitation mail (same as password recovery mail) being trigger for each created user.')
 p.add('--udm_api_password', env_var='UDM_API_PASSWORD', required=True, help='Password for the UDM REST API user.')
 p.add('--udm_api_username', env_var='UDM_API_USERNAME', default='Administrator', help='User to authentication against the UDM REST API with.')
+# p.add('--admin_enable_chat', env_var='ADMIN_ENABLE_CHAT', default=False, type=opt2bool, help='Optional: Set to "True" if users should get functional admin permissions for chat component - Requires openDesk Enterprise.')
+p.add(
+    "--admin_enable_fileshare",
+    env_var="ADMIN_ENABLE_FILESHARE",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should get functional admin permissions for fileshare component.',
+)
+p.add(
+    "--admin_enable_knowledgemanagement",
+    env_var="ADMIN_ENABLE_KNOWLEDGEMANAGEMENT",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should get functional admin permissions for knowledgemanagement component.',
+)
+p.add(
+    "--admin_enable_projectmanagement",
+    env_var="ADMIN_ENABLE_PROJECTMANAGEMENT",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should get functional admin permissions for projectmanagement component.',
+)
+p.add(
+    "--component_disable_fileshare",
+    env_var="COMPONENT_DISABLE_FILESHARE",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should not get the flag for fileshare access.',
+)
+p.add(
+    "--component_disable_groupware",
+    env_var="COMPONENT_DISABLE_GROUPWARE",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should not get the flag for groupware access.',
+)
+p.add(
+    "--component_disable_knowledgemanagement",
+    env_var="COMPONENT_DISABLE_KNOWLEDGEMANAGEMENT",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should not get the flag for knowledgemanagement access.',
+)
+p.add(
+    "--component_disable_livecollaboration",
+    env_var="COMPONENT_DISABLE_LIVECOLLABORATION",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should not get the flag for livecollaboration access.',
+)
+p.add(
+    "--component_disable_projectmanagement",
+    env_var="COMPONENT_DISABLE_PROJECTMANAGEMENT",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should not get the flag for projectmanagement access.',
+)
+p.add(
+    "--component_disable_videoconference",
+    env_var="COMPONENT_DISABLE_VIDEOCONFERENCE",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if users should not get the flag for videoconference access.',
+)
+p.add(
+    "--create_admin_accounts",
+    env_var="CREATE_ADMIN_ACCOUNTS",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if each user also should get an additional "<username>-admin" account',
+)
+p.add(
+    "--create_maildomains",
+    env_var="CREATE_MAILDOMAINS",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" to get non existing mail domains auto-created. Only relevant when importing a files in which mailPrimaryAddresses are provided that differ from the default (mail)domain.',
+)
+p.add(
+    "--create_oxcontexts",
+    env_var="CREATE_OXCONTEXT",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" to get non-existing OX contexts auto-created.',
+)
+p.add(
+    "--default_oxcontext",
+    env_var="DEFAULT_OXCONTEXT",
+    default=1,
+    type=int,
+    help="Optional: Set the default OX context ID to use when creating users. Default: 1",
+)
+p.add(
+    "--import_use_images",
+    env_var="IMPORT_USE_IMAGES",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if each user should be uploaded with a random profile picture when "import_filename" was set.',
+)
+p.add(
+    "--import_random_amount",
+    env_var="IMPORT_RANDOM_AMOUNT",
+    default=10,
+    type=int,
+    help='The number of random accounts to import if the "import_filename" was not set or found.',
+)
+p.add(
+    "--import_random_usernames",
+    env_var="IMPORT_RANDOM_USERNAMES",
+    default=True,
+    type=opt2bool,
+    help='If set to "False" the imported usernames of the imported follow the format user.N and admin.N.',
+)
+p.add(
+    "--reconcile_groups",
+    env_var="RECONCILE_GROUPS",
+    default=False,
+    type=opt2bool,
+    help=f'Optional: Set to "True" if groups on the users should be reconciled based on the input file. Will remove all groups from the user not defined in sheet except for the standard groups: {"; ".join(non_reconcile_groups)}'
+)
+p.add(
+    "--trigger_invitation_mail",
+    env_var="TRIGGER_INVITATION_MAIL",
+    default=False,
+    type=opt2bool,
+    help='Optional: Set to "True" if you want invitation mail (same as password recovery mail) being trigger for each created user.',
+)
 options = p.parse_args()
 
 new_user_password = options.set_default_password
@@ -95,8 +205,16 @@ ucs = Ucs(adm_username=options.udm_api_username, adm_password=options.udm_api_pa
 
 if not options.import_filename:
     logging.info(f"Starting random user import, as no file for import was defined.")
-    RandomUser(import_callback, create_admins=options.create_admin_accounts, amount=int(options.import_random_amount), password_reset_mail=options.password_recovery_email, randomize=options.import_random_usernames)
-    logging.info(f"Accounts that have been created:\n{ucs.get_imported_credentials_list()}")
+    RandomUser(
+        import_callback,
+        create_admin_accounts=options.create_admin_accounts,
+        amount=int(options.import_random_amount),
+        password_reset_mail=options.password_recovery_email,
+        randomize=options.import_random_usernames,
+    )
+    logging.info(
+        f"Accounts that have been created:\n{ucs.get_imported_credentials_list()}"
+    )
 elif os.path.isfile(options.import_filename):
     logging.info(f"Importing users from '{options.import_filename}'")
     ImportUser(import_callback, import_filename=options.import_filename, create_admin_accounts=options.create_admin_accounts, use_images=options.import_use_images)
-- 
GitLab