@@ -96,6 +96,7 @@ async def _websocket_call(self, data: dict) -> dict:
9696
9797 async for message in websocket :
9898 response = json .loads (message )
99+
99100 if "responseid" in response :
100101 if data ["responseid" ] == response ["responseid" ]:
101102 return response
@@ -108,6 +109,78 @@ def _send(self, data: dict) -> dict:
108109
109110 return asyncio .run (self ._websocket_call (data ))
110111
112+ def server_info (self ) -> dict :
113+ """Gets MeshCentral server info.
114+
115+ Returns:
116+ dict:
117+ Returns server info.
118+
119+ Example:
120+ {
121+ 'domain': '',
122+ 'name': 'mesh.example.com',
123+ 'mpsname': 'mesh.example.com',
124+ 'mpsport': 4433,
125+ 'port': 4443,
126+ 'emailcheck': True,
127+ 'domainauth': False,
128+ 'serverTime': 1645560067270,
129+ 'features': 9607777,
130+ 'features2': 16513,
131+ 'languages': ['en', 'cs', 'da', 'de', 'es', 'fi', 'fr', 'hi', 'it', 'ja', 'ko', 'nl', 'nn', 'pl', 'pt-br', 'pt', 'ru', 'sv', 'tr', 'zh-chs', 'zh-cht'],
132+ 'tlshash': '16D462CC0D306CFC7F242382A1606E2A57E6481B4EAAC7E5C6D91EFA306F9CABD0CD91566A8A35C3DA9580E1F51CF985',
133+ 'agentCertHash': 'V7IZUeuuIWMCY8e1SIb8fKqM1RkS4fUmCbCZzi4cMMzHAi3EJPi9Y8CP5XQfz2tZ',
134+ 'https': True,
135+ 'redirport': 8080,
136+ 'magenturl': 'mc://mesh.example.com:4443',
137+ 'domainsuffix': '',
138+ 'certExpire': 1652972190000
139+ }
140+ """
141+
142+ data = {
143+ "action" : "serverinfo"
144+ }
145+
146+ return self ._send (data )["serverinfo" ]
147+
148+ def user_info (self ) -> dict :
149+ """Gets logged on user info.
150+
151+ Returns:
152+ dict:
153+ Returns current user info
154+
155+ Example:
156+ {
157+ '_id': 'user//username',
158+ 'name': 'username',
159+ 'creation': 1643754241,
160+ 'links': {
161+ 'mesh//oAUeYE3HCqUFXWCkqwqfW@ElJ7orX6hrNv$r$RyCEsVgtUQNxYC6dLs4jlfQNTPA': {
162+ 'rights': 4294967295
163+ },
164+ 'mesh//$lhtFH8ZYcVEZYSqLx1O2vxqgSdzX9bjZLAbmRMz3lJ@XLulbyhqeRUPF4MbaN64': {
165+ 'rights': 4294967295
166+ }
167+ },
168+ 'email': 'example@example.com',
169+ 'emailVerified': True,
170+ 'siteadmin': 4294967295,
171+ 'pastlogin': 1645505345,
172+ 'access': 1645558617,
173+ 'login': 1645505346
174+ }
175+ """
176+
177+ data = {
178+ "action" : "userinfo"
179+ }
180+
181+ return self ._send (data )["userinfo" ]
182+
183+
111184 def get_device_group_id_by_name (self , group : str ) -> Optional [str ]:
112185 """Get the device group id by group name.
113186
@@ -347,6 +420,261 @@ def edit_device_group(
347420
348421 return self ._send (data )
349422
423+ def get_user_id_by_name (self , username : str ) -> Optional [str ]:
424+ """Get the user account id by username.
425+
426+ Args:
427+ username (str):
428+ Used to search through users.
429+
430+ Returns:
431+ str, None:
432+ Returns the user account _id if the username exists otherwise returns None.
433+ """
434+
435+ users = self .list_users ()
436+
437+ for user in users :
438+ if user ["username" ] == username :
439+ return user ["_id" ]
440+
441+ return None
442+
443+ def user_exists (
444+ self , username : Optional [str ] = None , id : Optional [str ] = None
445+ ) -> bool :
446+ """Check if a user account exists by username or id.
447+
448+ This method needs either user or id arguments set. If both are set then name
449+ takes precedence.
450+
451+ Args:
452+ username (str):
453+ Used to check if a device group with the same name exists.
454+ id (str):
455+ Used to check if a device group with the same id exists.
456+
457+ Returns:
458+ bool: True or False depending on if the user account exists.
459+ """
460+
461+ if not username and not id :
462+ raise ValueError ("Arguments username or id must be specified" )
463+
464+ users = self .list_users ()
465+
466+ for user in users :
467+ if user :
468+ if user ["name" ] == username :
469+ return True
470+ elif id :
471+ if user ["_id" ] == id :
472+ return True
473+
474+ return False
475+
476+ def list_users (self ) -> list :
477+ """List users
478+
479+ Returns:
480+ list: Mesh user accounts.
481+ """
482+
483+ data = {
484+ "action" : "users"
485+ }
486+
487+ return self ._send (data )["users" ]
488+
489+ def add_user (
490+ self ,
491+ username : str ,
492+ password : Optional [str ] = None ,
493+ random_pass : bool = False ,
494+ domain : Optional [str ] = None ,
495+ email : Optional [str ] = None ,
496+ email_verfied : bool = False ,
497+ reset_pass : bool = False ,
498+ full_name : Optional [str ] = None ,
499+ phone : Optional [str ] = None ,
500+ rights : Optional [str ] = None ,
501+ ) -> dict :
502+ """Add User
503+
504+ This method needs a username set and password is optional only is random_pass is true. random_pass
505+ will take precedence.
506+
507+ Args:
508+ username (str):
509+ Username for the user that is used to login
510+ password (str):
511+ Password to set for the user. Not needed if random_pass is set to True
512+ random_pass (str, optional):
513+ Sets a random password for the user account.
514+ domain (str, optional):
515+ Account domain, only for cross-domain admins.
516+ email (str, optional):
517+ New account email address.
518+ email_verified (bool, optional):
519+ New account email is verified.
520+ reset_pass (bool, optional):
521+ Request password reset on next login.
522+ full_name (str, optional):
523+ Set the full name for this account.
524+ phone (str, optional):
525+ Set the account phone number.
526+ rights (str, optional):
527+ Server permissions for account. Can be none, full, or a comma separated
528+ list of these possible values:
529+ manageusers,backup,restore,update,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents
530+
531+ Returns:
532+ dict: Returns a confirmation that the user was added
533+
534+ Example:
535+ {
536+ 'action': 'adduser',
537+ 'responseid': '31424b26-9539-400d-ab41-e406aeb337b2',
538+ 'result': 'ok'
539+ }
540+ """
541+
542+ if not password and not random_pass :
543+ raise ValueError ("Either password or random_pass must be set" )
544+
545+ data = {
546+ "action" : "adduser" ,
547+ "username" : username ,
548+ "pass" : utils .gen_password () if random_pass else password ,
549+ "responseid" : utils .gen_response_id (),
550+ }
551+
552+ if email :
553+ data ["email" ] = email
554+ if email_verfied :
555+ data ["emailVerified" ] = True
556+
557+ if reset_pass :
558+ data ["resetNextLogin" ] = True
559+
560+ if domain :
561+ data ["domain" ] = domain
562+
563+ if phone :
564+ data ["phone" ] = phone
565+
566+ if full_name :
567+ data ["realname" ] = full_name
568+
569+ if rights :
570+ data ["siteadmin" ] = utils .permissions_str_to_int (rights )
571+
572+ return self ._send (data )
573+
574+ def edit_user (
575+ self ,
576+ username : str ,
577+ domain : str = "" ,
578+ email : Optional [str ] = None ,
579+ email_verfied : bool = False ,
580+ reset_pass : bool = False ,
581+ full_name : Optional [str ] = None ,
582+ phone : Optional [str ] = None ,
583+ rights : Optional [str ] = None ,
584+ ) -> dict :
585+ """Edit User
586+
587+ This method needs a username set to identify the user to edit.
588+
589+ Args:
590+ username (str):
591+ Username for the user that is used to login
592+ domain (str, optional):
593+ Account domain, only for cross-domain admins. (defaults to '')
594+ email (str, optional):
595+ New account email address.
596+ email_verified (bool, optional):
597+ New account email is verified.
598+ reset_pass (bool, optional):
599+ Request password reset on next login.
600+ full_name (str, optional):
601+ Set the full name for this account.
602+ phone (str, optional):
603+ Set the account phone number.
604+ rights (str, optional):
605+ Server permissions for account. Can be none, full, or a comma separated
606+ list of these possible values:
607+ manageusers,backup,restore,update,fileaccess,locked,nonewgroups,notools,usergroups,recordings,locksettings,allevents
608+
609+ Returns:
610+ dict: Returns a confirmation that the user was edited
611+
612+ Example:
613+ {
614+ 'action': 'edituser',
615+ 'responseid': '1d508225-818d-444c-9a33-62c4ef76f652',
616+ 'result': 'ok'
617+ }
618+ """
619+
620+ data = {
621+ "action" : "edituser" ,
622+ "userid" : utils .format_user_id (username , domain ),
623+ "responseid" : utils .gen_response_id (),
624+ }
625+
626+ if email :
627+ data ["email" ] = email
628+ if email_verfied :
629+ data ["emailVerified" ] = True
630+
631+ if reset_pass :
632+ data ["resetNextLogin" ] = True
633+
634+ if domain :
635+ data ["domain" ] = domain
636+
637+ if phone :
638+ data ["phone" ] = phone
639+
640+ if full_name :
641+ data ["realname" ] = full_name
642+
643+ if rights :
644+ data ["siteadmin" ] = utils .permissions_str_to_int (rights )
645+
646+ return self ._send (data )
647+
648+ def remove_user (self , username : str , domain : str = "" ) -> dict :
649+ """Delete User
650+
651+ This method needs a username set to identify the user to delete.
652+
653+ Args:
654+ username (str):
655+ Username for the user that is used to login
656+ domain (str, optional)
657+ Account domain, only for cross-domain admins. (defaults to '')
658+
659+ Returns:
660+ dict: Returns a confirmation that the user was deleted.
661+
662+ Example:
663+ {
664+ 'action': 'deleteuser',
665+ 'responseid': '1d508225-818d-444c-9a33-62c4ef76f652',
666+ 'result': 'ok'
667+ }
668+ """
669+
670+ data = {
671+ "action" : "deleteuser" ,
672+ "userid" : utils .format_user_id (username , domain ),
673+ "responseid" : utils .gen_response_id (),
674+ }
675+
676+ return self ._send (data )
677+
350678 # run command on an agent
351679 def run_command (self , node_id : str , command : str , runAsUser : int = 0 ) -> dict :
352680
0 commit comments