From 4ff248df894ddb287c2a53c572971b7c7ece4fe8 Mon Sep 17 00:00:00 2001 From: Rajvardhan S Deshmukh Date: Tue, 15 Jul 2025 11:10:01 -0700 Subject: [PATCH 1/3] add user supports custom attributes --- duo_client/admin.py | 10 +++++++++- examples/Admin/create_user_and_phone.py | 3 +++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index 7deb218..97c97b7 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -843,7 +843,7 @@ def get_users_by_ids(self, user_ids): def add_user(self, username, realname=None, status=None, notes=None, email=None, firstname=None, lastname=None, alias1=None, alias2=None, alias3=None, alias4=None, - aliases=None): + aliases=None, custom_attribute_map=None): """ Adds a user. @@ -856,6 +856,9 @@ def add_user(self, username, realname=None, status=None, lastname - User's surname for ID Proofing (optional) alias1..alias4 - Aliases for the user's primary username (optional) aliases - Aliases for the user's primary username (optional) + custom_attribute_map - Map of custom attributes (optional). When provided this will be of type Dict[str|str]. e.g. + {"attribute_name":"attribute_value"} + Note: the custom attribute names have to be created prior to adding users Returns newly created user object. @@ -886,6 +889,11 @@ def add_user(self, username, realname=None, status=None, params['alias4'] = alias4 if aliases is not None: params['aliases'] = aliases + if isinstance(custom_attribute_map, dict): + for key in custom_attribute_map: + if isinstance(key, str) and isinstance(custom_attribute_map[key], str): + params[f'custom_attributes.{key}'] = custom_attribute_map[key] + response = self.json_api_call('POST', '/admin/v1/users', params) diff --git a/examples/Admin/create_user_and_phone.py b/examples/Admin/create_user_and_phone.py index c82d38f..cbfa606 100755 --- a/examples/Admin/create_user_and_phone.py +++ b/examples/Admin/create_user_and_phone.py @@ -1,6 +1,7 @@ #!/usr/bin/python import pprint import sys +import json import duo_client @@ -20,6 +21,7 @@ def get_next_arg(prompt): USERNAME = get_next_arg('user login name: ') REALNAME = get_next_arg('user full name: ') +CUSTOM_ATTRIBUTE_MAP_STR = get_next_arg('custom attribute map (optional): ') # Refer to http://www.duosecurity.com/docs/adminapi for more # information about phone types and platforms. @@ -31,6 +33,7 @@ def get_next_arg(prompt): user = admin_api.add_user( username=USERNAME, realname=REALNAME, + custom_attribute_map=json.loads(CUSTOM_ATTRIBUTE_MAP_STR), ) print('Created user:') pprint.pprint(user) From 0b1ec7793b311fa52a867ad057f8a545251630e3 Mon Sep 17 00:00:00 2001 From: Rajvardhan S Deshmukh Date: Tue, 15 Jul 2025 11:38:23 -0700 Subject: [PATCH 2/3] small fix --- examples/Admin/create_user_and_phone.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/examples/Admin/create_user_and_phone.py b/examples/Admin/create_user_and_phone.py index cbfa606..8303bea 100755 --- a/examples/Admin/create_user_and_phone.py +++ b/examples/Admin/create_user_and_phone.py @@ -33,7 +33,7 @@ def get_next_arg(prompt): user = admin_api.add_user( username=USERNAME, realname=REALNAME, - custom_attribute_map=json.loads(CUSTOM_ATTRIBUTE_MAP_STR), + custom_attribute_map=json.loads(CUSTOM_ATTRIBUTE_MAP_STR) if CUSTOM_ATTRIBUTE_MAP_STR else None , ) print('Created user:') pprint.pprint(user) From 0482fd206fc33ca61dd7b178ef18b20621fc0b41 Mon Sep 17 00:00:00 2001 From: Rajvardhan S Deshmukh Date: Wed, 16 Jul 2025 14:53:06 -0700 Subject: [PATCH 3/3] fail early --- duo_client/admin.py | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/duo_client/admin.py b/duo_client/admin.py index 97c97b7..9b7657d 100644 --- a/duo_client/admin.py +++ b/duo_client/admin.py @@ -889,10 +889,9 @@ def add_user(self, username, realname=None, status=None, params['alias4'] = alias4 if aliases is not None: params['aliases'] = aliases - if isinstance(custom_attribute_map, dict): + if custom_attribute_map is not None: for key in custom_attribute_map: - if isinstance(key, str) and isinstance(custom_attribute_map[key], str): - params[f'custom_attributes.{key}'] = custom_attribute_map[key] + params[f'custom_attributes.{key}'] = custom_attribute_map[key] response = self.json_api_call('POST', '/admin/v1/users',