From d28728ccc337fdaff7076c2b11b3d28ca934e574 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Mon, 29 Apr 2024 16:24:34 +0100 Subject: [PATCH] Nit 1204 ldap data refresh remove passwords (#42) * remove passwords command * formatting * Update user.py * Update user.py * Update user.py * Update format-python.yml * Update format-python.yml * Formatted code with black --line-length 120 * Update format-python.yml --------- Co-authored-by: github-actions <41898282+github-actions[bot]@users.noreply.github.com> --- .github/workflows/format-python.yml | 6 +- cli/__init__.py | 18 +++ cli/database/__init__.py | 4 +- cli/env.py | 16 +-- cli/git/__init__.py | 16 +-- cli/ldap_cmds/rbac.py | 2 +- cli/ldap_cmds/user.py | 173 ++++++++++++---------------- cli/logger.py | 14 +-- setup.py | 4 +- 9 files changed, 109 insertions(+), 144 deletions(-) diff --git a/.github/workflows/format-python.yml b/.github/workflows/format-python.yml index e4fefba..f5f84e1 100644 --- a/.github/workflows/format-python.yml +++ b/.github/workflows/format-python.yml @@ -1,8 +1,7 @@ name: Format Python on: pull_request: - types: - - [ opened, synchronize, reopened, edited, ready_for_review ] + types: [ opened, edited, reopened, synchronize, ready_for_review ] workflow_dispatch: jobs: format: @@ -10,7 +9,8 @@ jobs: steps: - uses: actions/checkout@v4 with: - fetch-depth: 0 + repository: ${{ github.event.pull_request.head.repo.full_name }} + ref: ${{ github.event.pull_request.head.ref }} - name: Format code with black run: | pip install black diff --git a/cli/__init__.py b/cli/__init__.py index c44e66b..1f0ae38 100644 --- a/cli/__init__.py +++ b/cli/__init__.py @@ -209,6 +209,23 @@ def user_expiry(user_ou, root_dn): cli.ldap_cmds.user.user_expiry(user_ou=user_ou, root_dn=root_dn) +@click.command() +@click.option( + "-u", + "--user-ou", + help="OU to add users to, defaults to ou=Users", + default="ou=Users", +) +@click.option( + "-r", + "--root-dn", + help="Root DN to add users to, defaults to dc=moj,dc=com", + default="dc=moj,dc=com", +) +def remove_all_user_passwords(user_ou, root_dn): + cli.ldap_cmds.user.remove_all_user_passwords(user_ou=user_ou, root_dn=root_dn) + + # from cli.ldap import test main_group.add_command(add_roles_to_users) @@ -217,6 +234,7 @@ def user_expiry(user_ou, root_dn): main_group.add_command(update_user_roles) main_group.add_command(deactivate_crc_users) main_group.add_command(user_expiry) +main_group.add_command(remove_all_user_passwords) logger.configure_logging() diff --git a/cli/database/__init__.py b/cli/database/__init__.py index 511366b..0e7dda6 100644 --- a/cli/database/__init__.py +++ b/cli/database/__init__.py @@ -13,7 +13,5 @@ def connection(): log.debug("Created database connection successfully") return conn except Exception as e: - log.exception( - f"Failed to create database connection. An exception of type {type(e).__name__} occurred: {e}" - ) + log.exception(f"Failed to create database connection. An exception of type {type(e).__name__} occurred: {e}") raise e diff --git a/cli/env.py b/cli/env.py index 98c815f..0b96acf 100644 --- a/cli/env.py +++ b/cli/env.py @@ -27,9 +27,7 @@ ).replace( "_DICT", "", - ): ast.literal_eval(val) - if "DICT" in key - else val + ): (ast.literal_eval(val) if "DICT" in key else val) for key, val in dotenv_values(".vars").items() if val is not None }, # load development variables @@ -40,9 +38,7 @@ ).replace( "_DICT", "", - ): ast.literal_eval(val) - if "DICT" in key - else val + ): (ast.literal_eval(val) if "DICT" in key else val) for key, val in os.environ.items() if key.startswith("VAR_") and val is not None }, @@ -61,9 +57,7 @@ .replace( "SSM_", "", - ): ast.literal_eval(val) - if "_DICT" in key - else val + ): (ast.literal_eval(val) if "_DICT" in key else val) for key, val in dotenv_values(".secrets").items() if val is not None }, @@ -79,9 +73,7 @@ .replace( "SSM_", "", - ): ast.literal_eval(val) - if "DICT" in key - else val + ): (ast.literal_eval(val) if "DICT" in key else val) for key, val in os.environ.items() if key.startswith("SECRET_") or key.startswith("SSM_") and val is not None }, diff --git a/cli/git/__init__.py b/cli/git/__init__.py index 8a733c9..6b6cf69 100644 --- a/cli/git/__init__.py +++ b/cli/git/__init__.py @@ -36,9 +36,7 @@ def get_access_token( headers=headers, ) except Exception as e: - logging.exception( - f"Failed to get access token. An exception of type {type(e).__name__} occurred: {e}" - ) + logging.exception(f"Failed to get access token. An exception of type {type(e).__name__} occurred: {e}") raise e # extract the token from the response @@ -68,9 +66,7 @@ def get_repo( multi_options=multi_options, ) except Exception as e: - logging.exception( - f"Failed to clone repo. An exception of type {type(e).__name__} occurred: {e}" - ) + logging.exception(f"Failed to clone repo. An exception of type {type(e).__name__} occurred: {e}") raise e # if there is a token, assume auth is required and use the token and auth_type elif token: @@ -83,9 +79,7 @@ def get_repo( multi_options=multi_options, ) except Exception as e: - logging.exception( - f"Failed to clone repo. An exception of type {type(e).__name__} occurred: {e}" - ) + logging.exception(f"Failed to clone repo. An exception of type {type(e).__name__} occurred: {e}") raise e # if there is no token, assume auth is not required and clone without else: @@ -97,7 +91,5 @@ def get_repo( multi_options=multi_options, ) except Exception as e: - logging.exception( - f"Failed to clone repo. An exception of type {type(e).__name__} occurred: {e}" - ) + logging.exception(f"Failed to clone repo. An exception of type {type(e).__name__} occurred: {e}") raise e diff --git a/cli/ldap_cmds/rbac.py b/cli/ldap_cmds/rbac.py index 77a7a57..4d83ec4 100644 --- a/cli/ldap_cmds/rbac.py +++ b/cli/ldap_cmds/rbac.py @@ -439,7 +439,7 @@ def user_ldifs( for file in user_files: records = ldif.LDIFRecordList(open(file, "rb")) records.parse() - + # pprint(records.all_records) # loop through the records for entry in records.all_records: diff --git a/cli/ldap_cmds/user.py b/cli/ldap_cmds/user.py index 2004124..3196ec8 100644 --- a/cli/ldap_cmds/user.py +++ b/cli/ldap_cmds/user.py @@ -46,7 +46,9 @@ def change_home_areas( env.secrets.get("LDAP_BIND_PASSWORD"), ) - search_filter = f"(&(objectclass={object_class})(userHomeArea={old_home_area})(!(cn={old_home_area}))(!(endDate=*)))" + search_filter = ( + f"(&(objectclass={object_class})(userHomeArea={old_home_area})(!(cn={old_home_area}))(!(endDate=*)))" + ) ldap_connection.search( ",".join( [ @@ -78,9 +80,7 @@ def change_home_areas( if ldap_connection.result["result"] == 0: log.info(f"Successfully updated {attribute} for {dn}") else: - log.error( - f"Failed to update {attribute} for {dn}: {ldap_connection.result}" - ) + log.error(f"Failed to update {attribute} for {dn}: {ldap_connection.result}") ######################################### @@ -96,33 +96,20 @@ def parse_user_role_list( # and the roles are separated by a semi-colon: # username1,role1;role2;role3|username2,role1;role2 - return { - user.split(",")[0]: user.split(",")[1].split(";") - for user in user_role_list.split("|") - } + return {user.split(",")[0]: user.split(",")[1].split(";") for user in user_role_list.split("|")} -def add_roles_to_user( - username, - roles, - user_ou="ou=Users", - root_dn="dc=moj,dc=com", -): +def add_roles_to_user(username, roles, user_ou="ou=Users", root_dn="dc=moj,dc=com"): log.info(f"Adding roles {roles} to user {username}") ldap_connection = ldap_connect( - env.vars.get("LDAP_HOST"), - env.vars.get("LDAP_USER"), - env.secrets.get("LDAP_BIND_PASSWORD"), + env.vars.get("LDAP_HOST"), env.vars.get("LDAP_USER"), env.secrets.get("LDAP_BIND_PASSWORD") ) for role in roles: try: ldap_connection.add( f"cn={role},cn={username},{user_ou},{root_dn}", attributes={ - "objectClass": [ - "NDRoleAssociation", - "alias", - ], + "objectClass": ["NDRoleAssociation", "alias"], "aliasedObjectName": f"cn={role},cn={username},cn=ndRoleCatalogue,{user_ou},{root_dn}", }, ) @@ -140,11 +127,7 @@ def add_roles_to_user( raise Exception(f"Failed to add role {role} to user {username}") -def process_user_roles_list( - user_role_list, - user_ou="ou=Users", - root_dn="dc=moj,dc=com", -): +def process_user_roles_list(user_role_list, user_ou="ou=Users", root_dn="dc=moj,dc=com"): user_roles = parse_user_role_list(user_role_list) try: for ( @@ -168,15 +151,7 @@ def process_user_roles_list( def update_roles( - roles, - user_ou, - root_dn, - add, - remove, - update_notes, - user_note, - user_filter="(userSector=*)", - role_filter="*", + roles, user_ou, root_dn, add, remove, update_notes, user_note, user_filter="(userSector=*)", role_filter="*" ): if update_notes and (user_note is None or len(user_note) < 1): log.error("User note must be provided when updating notes") @@ -195,12 +170,7 @@ def update_roles( # # Search for users matching the user_filter try: ldap_connection_user_filter.search( - ",".join( - [ - user_ou, - root_dn, - ] - ), + ",".join([user_ou, root_dn]), user_filter, attributes=["cn"], ) @@ -208,13 +178,7 @@ def update_roles( log.exception("Failed to search for users") raise e - users_found = sorted( - [ - entry.cn.value - for entry in ldap_connection_user_filter.entries - if entry.cn.value - ] - ) + users_found = sorted([entry.cn.value for entry in ldap_connection_user_filter.entries if entry.cn.value]) log.debug("users found from user filter") log.debug(users_found) ldap_connection_user_filter.unbind() @@ -224,7 +188,9 @@ def update_roles( # create role filter if len(roles_filter_list) > 0: - full_role_filter = f"(&(objectclass=NDRoleAssociation)(|{''.join(['(cn=' + role + ')' for role in roles_filter_list])}))" + full_role_filter = ( + f"(&(objectclass=NDRoleAssociation)(|{''.join(['(cn=' + role + ')' for role in roles_filter_list])}))" + ) else: full_role_filter = "(&(objectclass=NDRoleAssociation)(cn=*))" @@ -242,12 +208,7 @@ def update_roles( try: ldap_connection_role_filter.search( - ",".join( - [ - user_ou, - root_dn, - ] - ), + ",".join([user_ou, root_dn]), full_role_filter, attributes=["cn"], dereference_aliases=DEREF_NEVER, @@ -257,12 +218,7 @@ def update_roles( raise e roles_found = sorted( - set( - { - entry.entry_dn.split(",")[1].split("=")[1] - for entry in ldap_connection_role_filter.entries - } - ) + set({entry.entry_dn.split(",")[1].split("=")[1] for entry in ldap_connection_role_filter.entries}) ) log.debug("users found from roles filter: ") log.debug(roles_found) @@ -276,12 +232,7 @@ def update_roles( # cartesian_product = [(user, role) for user in matched_users for role in roles] - cartesian_product = list( - product( - matched_users, - roles, - ) - ) + cartesian_product = list(product(matched_users, roles)) log.debug("cartesian product: ") log.debug(cartesian_product) @@ -303,11 +254,7 @@ def update_roles( attributes={ "cn": item[1], "aliasedObjectName": f"cn={item[1]},cn=ndRoleCatalogue,{user_ou},{root_dn}", - "objectClass": [ - "NDRoleAssociation", - "alias", - "top", - ], + "objectClass": ["NDRoleAssociation", "alias", "top"], }, ) except Exception as e: @@ -321,9 +268,7 @@ def update_roles( log.e(f"Failed to add role '{item[1]}' to user '{item[0]}'") log.debug(ldap_connection_action.result) elif remove: - ldap_connection_action.delete( - f"cn={item[1]},cn={item[0]},{user_ou},{root_dn}" - ) + ldap_connection_action.delete(f"cn={item[1]},cn={item[0]},{user_ou},{root_dn}") if ldap_connection_action.result["result"] == 0: log.info(f"Successfully removed role '{item[1]}' from user '{item[0]}'") elif ldap_connection_action.result["result"] == 32: @@ -391,10 +336,7 @@ def update_roles( ######################################### -def deactivate_crc_users( - user_ou, - root_dn, -): +def deactivate_crc_users(user_ou, root_dn): log.info("Deactivating CRC users") ldap_connection = ldap_connect( env.vars.get("LDAP_HOST"), @@ -402,9 +344,7 @@ def deactivate_crc_users( env.secrets.get("LDAP_BIND_PASSWORD"), ) - user_filter = ( - "(userSector=private)(!(userSector=public))(!(endDate=*))(objectclass=NDUser)" - ) + user_filter = "(userSector=private)(!(userSector=public))(!(endDate=*))(objectclass=NDUser)" home_areas = [ [ @@ -448,12 +388,7 @@ def deactivate_crc_users( found_users.append(entry.entry_dn for entry in ldap_connection.entries) ldap_connection.search( - ",".join( - [ - user_ou, - root_dn, - ] - ), + ",".join([user_ou, root_dn]), f"(&(!(userHomeArea=*)){user_filter})", attributes=["dn"], ) @@ -479,7 +414,9 @@ def deactivate_crc_users( connection = cli.database.connection() for user_dn in all_users: try: - update_sql = f"UPDATE USER_ SET END_DATE=TRUNC(CURRENT_DATE) WHERE UPPER(DISTINGUISHED_NAME)=UPPER(:user_dn)" + update_sql = ( + f"UPDATE USER_ SET END_DATE=TRUNC(CURRENT_DATE) WHERE UPPER(DISTINGUISHED_NAME)=UPPER(:user_dn)" + ) update_cursor = connection.cursor() update_cursor.execute( update_sql, @@ -494,10 +431,7 @@ def deactivate_crc_users( connection.close() -def user_expiry( - user_ou, - root_dn, -): +def user_expiry(user_ou, root_dn): date_str = f"{datetime.now().strftime('%Y%m%d')}000000Z" log.info(f"Expiring users with end date {date_str}") @@ -547,12 +481,7 @@ def user_expiry( try: ldap_connection_unlock.search( - ",".join( - [ - user_ou, - root_dn, - ] - ), + ",".join([user_ou, root_dn]), f"(&(pwdAccountLockedTime=000001010000Z)(|(!(endDate=*))(endDate>={date_str}))(|(!(startDate=*))(startDate<={date_str})))", attributes=["cn"], ) @@ -577,3 +506,49 @@ def user_expiry( log.info(f"Unlocked user {user}") except Exception as e: log.exception(f"Failed to unlock user {user} \n Exception: {e}") + + +def remove_all_user_passwords(user_ou, root_dn): + log.info("Removing all user passwords") + + ldap_connection = ldap_connect( + env.vars.get("LDAP_HOST"), + env.vars.get("LDAP_USER"), + env.secrets.get("LDAP_BIND_PASSWORD"), + ) + + user_filter = "(!(cn=AutomatedTestUser))" + + try: + ldap_connection.search( + ",".join([user_ou, root_dn]), + user_filter, + attributes=["cn"], + search_scope="LEVEL", + ) + except Exception as e: + log.exception("Failed to search for users") + raise e + + found_users = [entry.entry_dn for entry in ldap_connection.entries] + log.debug("Users found:") + log.debug(found_users) + + for user in found_users: + try: + ldap_connection.modify( + user, + { + "userPassword": [ + ( + MODIFY_DELETE, + [], + ) + ] + }, + ) + log.info(f"Successfully removed passwd for user {user}, or it didn't have one to begin with") + except Exception as e: + log.exception(f"Failed to remove passwd for user {user}") + raise e + ldap_connection.unbind() diff --git a/cli/logger.py b/cli/logger.py index a76bb6a..16afdf1 100644 --- a/cli/logger.py +++ b/cli/logger.py @@ -15,9 +15,7 @@ def __init__( fmt=format_str, datefmt=datefmt_str, ) - self._secrets_set = set( - cli.env.secrets.values() - ) # Retrieve secrets set here + self._secrets_set = set(cli.env.secrets.values()) # Retrieve secrets set here self.default_msec_format = "%s.%03d" def _filter( @@ -25,10 +23,7 @@ def _filter( s, ): redacted = " ".join( - [ - "*" * len(string) if string in self._secrets_set else string - for string in s.split(" ") - ] + ["*" * len(string) if string in self._secrets_set else string for string in s.split(" ")] ) return redacted @@ -42,10 +37,7 @@ def format( print("configure_logging") """Configure logging based on environment variables.""" - format = ( - cli.env.vars.get("LOG_FORMAT") - or "%(asctime)s.%(msecs)03d - %(levelname)s: %(message)s" - ) + format = cli.env.vars.get("LOG_FORMAT") or "%(asctime)s.%(msecs)03d - %(levelname)s: %(message)s" datefmt = cli.env.vars.get("LOG_DATE_FORMAT") or "%Y-%m-%d %H:%M:%S" log = logging.getLogger(__name__) diff --git a/setup.py b/setup.py index b28c4dc..10818b2 100644 --- a/setup.py +++ b/setup.py @@ -9,9 +9,7 @@ standard_pkgs = [r for r in requirements if not r.startswith("git+")] git_pkgs = [r for r in requirements if r.startswith("git+")] -formatted_git_pkgs = [ - f"{git_pkg.split('/')[-1].split('.git@')[0]} @ {git_pkg}" for git_pkg in git_pkgs -] +formatted_git_pkgs = [f"{git_pkg.split('/')[-1].split('.git@')[0]} @ {git_pkg}" for git_pkg in git_pkgs] all_reqs = standard_pkgs + formatted_git_pkgs setup(