on 02-16-2025 01:29 PM
As the administrator, you might want to modify your users’ roles to have more granularity. With multiple roles per user, the user has multiple roles, so that you don’t have to give more privileges than are necessary. This use case updates users with the specified roles. A CSV file that contains the user’s email address and the roles to be assigned to the user.
The example code, update_roles.py, obtains a list of users and a list of roles. Each line on the CSV file is read, and the user email is mapped to a user ID and the role name is mapped to a role ID. With the user ID and the role IDs, the user’s roles are updated.
Looking at the code, you’ll notice that there is a Python class, User.
7 # User class with email, user ID, and role IDs. 8 class User: 9 def __init__(self, user): 10 self.email = user['email'] 11 self.id = user['id'] 12 self.role_ids = list(user['role_ids']) 13 self.role_ids.sort() 14 15 def get_id(self): 16 return self.id 17 18 def get_role_ids(self): 19 return self.role_ids
This is the first time I’ve used a class. I like using a class to contain information about an object and then hash to the objection with a dictionary. The User class stores the user’s email address, ID, and sorted role IDs. It is straightforward to add more information to the class if required.
The function get_user_ids() obtains a dictionary of users hashed by the user’s email address.
21 # Returns a dictionary of user's email to user object. 22 def get_user_ids(base_url, headers): 23 users = {} 24 list_users_url = f"{base_url}users" 25 26 response = requests.get(list_users_url, headers=headers) 27 if response.status_code != 200: 28 print(f"List Users Error: {response.status_code} with {list_users_url}") 29 sys.exit(1) 30 31 resp_json = response.json() 32 users_resp = resp_json['users'] 33 34 for user in users_resp: 35 #print(f"{user['email']} : {user['id']}") 36 a_user = User(user) 37 users[user['email']] = a_user 38 39 return users
The function invokes the List Users API. For each user, a User object is created with the desired information. The newly created object is pointed to by a dictionary with the user’s email address as the key.. As you can see I have left some debugging print statements in the code.
The function get_role_ids() is very similar to get_user_ids(), except that in get_role_ids() the List Roles API is invoked and the dictionary maps to the role ID, not a User object.
Our next function, map_role_names_to_ids(), takes the role name to ID dictionary and a list of role names as input parameters to return a list of valid role IDs.
60 # Returns the role IDs for a list of role_name using the role_name_to_id dictionary. 61 def map_role_names_to_ids(role_name_to_id, role_names): 62 role_ids = [] 63 role_names = [role.strip(' ') for role in role_names] 64 65 for role_name in role_names: 66 role_id = role_name_to_id.get(role_name) 67 if not role_id: 68 print(f"{role_name} is not on system") 69 continue 70 role_ids.append(role_id) 71 72 role_ids.sort() 73 return role_ids
Note the function verifies if the role name exists and sorts the role IDs. The reason for sorting the role IDs is for comparison purposes later on. If the role name is invalid, a warning message is printed and the function carries on.
Finally, we get to update_user() which updates one user specified by user ID and the role IDs to update by invoking the Update User API. Updating the role IDs completely obliterates the current role IDs. If you want to add role IDs to the current roles ID(s), then you’ll have to obtain the current role IDs and concatenate them with the new role IDs.
75 # Updates the user with new roles. 76 def update_user(base_url, headers, user_id, role_ids_to_update): 77 update_user_url = f"{base_url}users/{user_id}" 78 79 update_params = { 80 "user": { 81 "role_ids": role_ids_to_update 82 } 83 } 84 85 print(f"Updating: {user_id} - {role_ids_to_update}") 86 87 response = requests.put(update_user_url, headers=headers, data=json.dumps(update_params)) 88 if response.status_code != 204: 89 print(f"Update User Error: {response.status_code} with {update_user_url}") 90 return 91
You could update the user with role names, but I prefer role IDs, because role IDs are used in API URLs. IDs are the coin of the API realm.
Note that the default CSV file name is user_roles.csv, and that it can be modified by the first command input parameter.
97 csv_file_name = "user_roles.csv" 98 if len(sys.argv) > 1: 99 csv_file_name = sys.argv[1]
The first thing to do is get the user and role dictionaries.
115 # Obtain the mapping dictionaries 116 user_name_to_id = get_user_ids(base_url, headers) 117 role_name_to_id = get_role_ids(base_url, headers)
The CSV file is read and each user is processed:
119 # Read each row in the CSV file and process. Skip over comments. 120 with open(csv_file_name, 'r') as reader_obj: 121 csv_reader = reader(reader_obj) 122 for row in csv_reader: 123 if row[0].startswith('#'124 continue 125 print(f"Processing: {row[0]} - {row[1:]}") 126 email_from_csv = row[0] 127 user_obj = user_name_to_id.get(email_from_csv) 128 if not user_obj: 129 print(f"{email_from_csv} is not a valid user") 130 continue 131 132 roles_from_csv = row[1:] 133 role_ids_from_csv = map_role_names_to_ids(role_name_to_id, roles_from_csv) 134 #print(f"{email_from_csv} : {role_ids_from_csv}") 135 136 curr_roles = user_obj.get_role_ids() 137 if curr_roles == role_ids_from_csv: 138 print("Current roles are the same as roles to update.") 139 else: 140 update_user(base_url, headers, user_obj.get_id(), role_ids_from_csv)
The code above uses reader from the Python CSV library. For each CSV row:
Notice that the code can be used to verify the role IDs are correct as well as updating.
Multiple roles per user allow you control user permissions with more granularity; and this script makes it straightforward to update each user.
If you’re interested in playing with these samples, they’re located in the Kenna Security blog_samples repo in the python/user_roles directory. By the way, I left the list_user_roles.py script that lists the role IDs for each user that I used for verification. Also in Kenna’s All_Samples repository, there is an add_user sample in Ruby.
Find answers to your questions by entering keywords or phrases in the Search bar above. New here? Use these resources to familiarize yourself with the community: