From 7e19a3107d3b3340dacb776c90e1591860f5e1e8 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:24:29 +0100 Subject: [PATCH 1/9] remove passwords command --- cli/__init__.py | 18 ++++++++ cli/ldap_cmds/user.py | 98 ++++++++++++++++++++++++++++++------------- 2 files changed, 87 insertions(+), 29 deletions(-) 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/ldap_cmds/user.py b/cli/ldap_cmds/user.py index 2004124..7a49aaf 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,10 +96,7 @@ 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( @@ -208,13 +205,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 +215,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=*))" @@ -257,12 +250,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) @@ -321,9 +309,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: @@ -402,9 +388,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 = [ [ @@ -479,7 +463,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, @@ -577,3 +563,57 @@ 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 from user filter") + 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() From a944171783197f68ad82fb440f433fc67001cf5c Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:27:02 +0100 Subject: [PATCH 2/9] formatting --- cli/database/__init__.py | 4 +--- cli/git/__init__.py | 16 ++++------------ cli/ldap_cmds/rbac.py | 2 +- cli/logger.py | 14 +++----------- setup.py | 4 +--- 5 files changed, 10 insertions(+), 30 deletions(-) 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/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/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( From b48bde713080c0bcc393bc95049f12ad49f7d13c Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:33:29 +0100 Subject: [PATCH 3/9] Update user.py --- cli/ldap_cmds/user.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cli/ldap_cmds/user.py b/cli/ldap_cmds/user.py index 7a49aaf..1a5d11b 100644 --- a/cli/ldap_cmds/user.py +++ b/cli/ldap_cmds/user.py @@ -596,7 +596,7 @@ def remove_all_user_passwords( raise e found_users = [entry.entry_dn for entry in ldap_connection.entries] - log.debug("users found from user filter") + log.debug("Users found:") log.debug(found_users) for user in found_users: From be1962226054fc394f20857595e2856172f02890 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:37:09 +0100 Subject: [PATCH 4/9] Update user.py --- cli/ldap_cmds/user.py | 89 +++++++------------------------------------ 1 file changed, 14 insertions(+), 75 deletions(-) diff --git a/cli/ldap_cmds/user.py b/cli/ldap_cmds/user.py index 1a5d11b..27fa809 100644 --- a/cli/ldap_cmds/user.py +++ b/cli/ldap_cmds/user.py @@ -99,27 +99,17 @@ def parse_user_role_list( 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}", }, ) @@ -165,15 +155,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") @@ -192,12 +174,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"], ) @@ -235,12 +212,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, @@ -264,12 +236,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) @@ -291,11 +258,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: @@ -377,10 +340,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"), @@ -432,12 +392,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"], ) @@ -480,10 +435,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}") @@ -533,12 +485,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"], ) @@ -565,10 +512,7 @@ def user_expiry( log.exception(f"Failed to unlock user {user} \n Exception: {e}") -def remove_all_user_passwords( - user_ou, - root_dn, -): +def remove_all_user_passwords(user_ou, root_dn): log.info("Removing all user passwords") ldap_connection = ldap_connect( @@ -581,12 +525,7 @@ def remove_all_user_passwords( try: ldap_connection.search( - ",".join( - [ - user_ou, - root_dn, - ] - ), + ",".join([user_ou, root_dn]), user_filter, attributes=["cn"], search_scope="LEVEL", From f89f9f39db288cc99ce5b00ee167aaec29ff4733 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:43:38 +0100 Subject: [PATCH 5/9] Update user.py --- cli/ldap_cmds/user.py | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/cli/ldap_cmds/user.py b/cli/ldap_cmds/user.py index 27fa809..3196ec8 100644 --- a/cli/ldap_cmds/user.py +++ b/cli/ldap_cmds/user.py @@ -127,11 +127,7 @@ def add_roles_to_user(username, roles, user_ou="ou=Users", root_dn="dc=moj,dc=co 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 ( From 21c2b1c80d052613f05e6963230e7ec36e9839f4 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:48:58 +0100 Subject: [PATCH 6/9] Update format-python.yml --- .github/workflows/format-python.yml | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/workflows/format-python.yml b/.github/workflows/format-python.yml index dd15608..52856f8 100644 --- a/.github/workflows/format-python.yml +++ b/.github/workflows/format-python.yml @@ -9,8 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 - with: - fetch-depth: 0 - name: Format code with black run: | pip install black From 5c4702029aaec693cec6fc7996b340c223b279ed Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 19:51:31 +0100 Subject: [PATCH 7/9] Update format-python.yml --- .github/workflows/format-python.yml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.github/workflows/format-python.yml b/.github/workflows/format-python.yml index 52856f8..d84f02f 100644 --- a/.github/workflows/format-python.yml +++ b/.github/workflows/format-python.yml @@ -9,6 +9,9 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + 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 From a1e1e49b4bccc5de1d3cf913aa85751f4065d37a Mon Sep 17 00:00:00 2001 From: github-actions <41898282+github-actions[bot]@users.noreply.github.com> Date: Thu, 25 Apr 2024 18:51:50 +0000 Subject: [PATCH 8/9] Formatted code with black --line-length 120 --- cli/env.py | 16 ++++------------ 1 file changed, 4 insertions(+), 12 deletions(-) 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 }, From 6826669c0499bdb8de29c04c02a7f18acc715d61 Mon Sep 17 00:00:00 2001 From: George Taylor Date: Thu, 25 Apr 2024 20:09:47 +0100 Subject: [PATCH 9/9] Update format-python.yml --- .github/workflows/format-python.yml | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/.github/workflows/format-python.yml b/.github/workflows/format-python.yml index cba4225..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: