@@ -125,7 +125,7 @@ def _apply_group_changes(current, add_groups, remove_groups):
125125
126126
127127def _resolve_user_id (user_id , api_url , token ):
128- if "@" not in user_id :
128+ if _looks_like_object_id ( user_id ) :
129129 return user_id
130130 status , body = _request_json ("GET" , f"{ api_url } /users" , token = token )
131131 if status >= 400 :
@@ -135,20 +135,20 @@ def _resolve_user_id(user_id, api_url, token):
135135 payload = json .loads (body ) if body else []
136136 except json .JSONDecodeError as exc :
137137 raise SystemExit ("Failed to parse users response" ) from exc
138- if not isinstance (payload , list ):
139- raise SystemExit ( "Unexpected users response" )
140- matches = [
141- user
142- for user in payload
143- if isinstance ( user , dict ) and user .get ("email " ) == user_id
144- ]
138+ items = _parse_paginated_items (payload )
139+ matches = []
140+ for user in items :
141+ if not isinstance ( user , dict ):
142+ continue
143+ if user . get ( "email" ) == user_id or user .get ("username " ) == user_id :
144+ matches . append ( user )
145145 if not matches :
146- raise SystemExit (f"No user found with email: { user_id } " )
146+ raise SystemExit (f"No user found with email/username : { user_id } " )
147147 if len (matches ) > 1 :
148- raise SystemExit (f"Multiple users found with email: { user_id } " )
148+ raise SystemExit (f"Multiple users found with email/username : { user_id } " )
149149 resolved_id = matches [0 ].get ("id" )
150150 if not resolved_id :
151- raise SystemExit (f"User with email { user_id } has no id" )
151+ raise SystemExit (f"User with { user_id } has no id" )
152152 return resolved_id
153153
154154
@@ -194,6 +194,47 @@ def _resolve_group_id(group_id, api_url, token):
194194 return resolved_id
195195
196196
197+ def _resolve_group_name (group_name , api_url , token ):
198+ if not _looks_like_object_id (group_name ):
199+ return group_name
200+ status , body = _request_json (
201+ "GET" , f"{ api_url } /user-groups/{ group_name } " , token = token
202+ )
203+ if status >= 400 :
204+ _print_response (status , body )
205+ raise SystemExit (1 )
206+ try :
207+ payload = json .loads (body ) if body else {}
208+ except json .JSONDecodeError as exc :
209+ raise SystemExit ("Failed to parse user-group response" ) from exc
210+ resolved_name = payload .get ("name" )
211+ if not resolved_name :
212+ raise SystemExit (f"Group { group_name } has no name" )
213+ return resolved_name
214+
215+
216+ def _resolve_group_names (group_names , api_url , token ):
217+ return _dedupe (
218+ [_resolve_group_name (name , api_url , token ) for name in group_names ]
219+ )
220+
221+
222+ def _update_user_groups (resolved_id , add_groups , remove_groups , api_url , token ):
223+ status , body = _request_json ("GET" , f"{ api_url } /user/{ resolved_id } " , token = token )
224+ if status >= 400 :
225+ _print_response (status , body )
226+ raise SystemExit (1 )
227+ try :
228+ payload = json .loads (body ) if body else {}
229+ except json .JSONDecodeError as exc :
230+ raise SystemExit ("Failed to parse user response" ) from exc
231+ current_groups = _extract_group_names (payload )
232+ data = {
233+ "groups" : _apply_group_changes (current_groups , add_groups , remove_groups ),
234+ }
235+ return _request_json ("PATCH" , f"{ api_url } /user/{ resolved_id } " , data , token = token )
236+
237+
197238def _request_json (method , url , data = None , token = None , form = False ):
198239 headers = {"accept" : "application/json" }
199240 body = None
@@ -352,6 +393,28 @@ def main():
352393 delete_user = subparsers .add_parser ("delete-user" , help = "Delete user by id" )
353394 delete_user .add_argument ("user_id" )
354395
396+ assign_group = subparsers .add_parser (
397+ "assign-group" , help = "Assign group(s) to a user"
398+ )
399+ assign_group .add_argument ("user_id" )
400+ assign_group .add_argument (
401+ "--group" ,
402+ action = "append" ,
403+ default = [],
404+ help = "Group name or id; can be used multiple times or with commas" ,
405+ )
406+
407+ deassign_group = subparsers .add_parser (
408+ "deassign-group" , help = "Remove group(s) from a user"
409+ )
410+ deassign_group .add_argument ("user_id" )
411+ deassign_group .add_argument (
412+ "--group" ,
413+ action = "append" ,
414+ default = [],
415+ help = "Group name or id; can be used multiple times or with commas" ,
416+ )
417+
355418 list_groups = subparsers .add_parser ("list-groups" , help = "List user groups" )
356419
357420 get_group = subparsers .add_parser ("get-group" , help = "Get user group by id or name" )
@@ -416,6 +479,8 @@ def main():
416479 "get-user" ,
417480 "update-user" ,
418481 "delete-user" ,
482+ "assign-group" ,
483+ "deassign-group" ,
419484 "list-groups" ,
420485 "get-group" ,
421486 "create-group" ,
@@ -500,6 +565,12 @@ def main():
500565 set_groups = _parse_group_list (args .set_groups )
501566 add_groups = _parse_group_list (args .add_group )
502567 remove_groups = _parse_group_list (args .remove_group )
568+ if set_groups :
569+ set_groups = _resolve_group_names (set_groups , api_url , token )
570+ if add_groups :
571+ add_groups = _resolve_group_names (add_groups , api_url , token )
572+ if remove_groups :
573+ remove_groups = _resolve_group_names (remove_groups , api_url , token )
503574 if set_groups or add_groups or remove_groups :
504575 if set_groups :
505576 current_groups = set_groups
@@ -529,6 +600,24 @@ def main():
529600 status , body = _request_json (
530601 "DELETE" , f"{ api_url } /user/{ resolved_id } " , token = token
531602 )
603+ elif args .command == "assign-group" :
604+ resolved_id = _resolve_user_id (args .user_id , api_url , token )
605+ add_groups = _parse_group_list (args .group )
606+ if not add_groups :
607+ raise SystemExit ("No groups specified. Use --group." )
608+ add_groups = _resolve_group_names (add_groups , api_url , token )
609+ status , body = _update_user_groups (
610+ resolved_id , add_groups , [], api_url , token
611+ )
612+ elif args .command == "deassign-group" :
613+ resolved_id = _resolve_user_id (args .user_id , api_url , token )
614+ remove_groups = _parse_group_list (args .group )
615+ if not remove_groups :
616+ raise SystemExit ("No groups specified. Use --group." )
617+ remove_groups = _resolve_group_names (remove_groups , api_url , token )
618+ status , body = _update_user_groups (
619+ resolved_id , [], remove_groups , api_url , token
620+ )
532621 elif args .command == "list-groups" :
533622 status , body = _request_json ("GET" , f"{ api_url } /user-groups" , token = token )
534623 elif args .command == "get-group" :
0 commit comments