Updating Users with Multiple Roles

Sep 30, 2021
Rick Ehrhart
API Evangelist

Share with Your Network

Did you know that last month Kenna Security released a new feature, multiple roles per user?  What does this mean for Kenna’s User APIs?  Per Katie Kolon’s article, “Understanding Multiple Roles per User”, the following User APIs have been modified:

As stated in the article, the changes have impacts on your current code that uses the above User APIs.  Basically, the role field has been changed to roles.  By the way, Delete User has not changed. 

Now that that is out of the way, let’s look at a use case. 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.

Class user

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()
 15     def get_id(self):
 16         return self.id
 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.

Obtaining the roles and users

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"
 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)
 31     resp_json = response.json()
 32     users_resp = resp_json['users']
 34     for user in users_resp:
 35         #print(f"{user['email']} : {user['id']}")
 36         a_user = User(user)
 37         users[user['email']] = a_user
 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]
 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)
 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.

Update user

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}"
 79     update_params = {
 80         "user": {
 81             "role_ids": role_ids_to_update
 82         }
 83     }
 85     print(f"Updating: {user_id} - {role_ids_to_update}")
 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

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.

Tying it all together

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
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}")
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:

  • Skip comments.
  • Assign the first column to the email address.
  • Validate the user’s email address.
  • Assign the rest of columns to role names.
  • Map role names to role IDs.
  • If the role IDs from the CSV file are the same as the user’s current role IDs, print an informative message; else update the user with the new role IDs.

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.

Rick Ehrhart

 API Evangelist


Read the Latest Content

Vulnerability Management

How to Build a Vulnerability Management Program

Get the details on the 6 critical steps to build an effective vulnerability program. Read more about how vulnerability management can help your business.
Kenna API

Kenna Security API Postman Collection

The new Kenna API Postman Collection gives you a jump start on your explorations by providing templates for many of the Kenna Security APIs.
Kenna API

Acquiring Vulnerabilities per Asset

With Kenna APIs, you can extract assets and its vulnerability data, let's take a look at different strategies for different numbers of assets.

© 2022 Kenna Security. All Rights Reserved. Privacy Policy.