Files
pylibmeshctrl/src/meshctrl/session.py

1176 lines
47 KiB
Python
Raw Normal View History

2024-11-01 12:00:16 -07:00
import websockets
import websockets.datastructures
import websockets.asyncio
import websockets.asyncio.client
import asyncio
import base64
import json
from . import constants
from . import exceptions
from . import util
def _check_socket(f):
async def wrapper(self, *args, **kwargs):
await self.initialized.wait()
if not self.alive:
raise self._main_loop_error
return await f(self, *args, **kwargs)
return wrapper
class Session(object):
'''
Class for MeshCentral control session
Args:
url (str): URL of meshcentral server to connect to. Should start with either "ws://" or "wss://".
user (str): Username of to use for connecting. Can also be username generated from token.
domain (str): Domain to connect to
password (str): Password with which to connect. Can also be password generated from token.
loginkey (str|bytes): Key from already handled login. Overrides username/password.
proxy (str): "url:port" to use for proxy server
token (str): Login token. This appears to be superfluous
ignore_ssl (bool): Ignore SSL errors
Returns:
:py:class:`Session`: Session connected to url
Attributes:
url (str): url to which the session is connected
initialized (asyncio.Event): Event marking if the Session initialization has finished. Wait on this to wait for a connection.
alive (bool): Whether the session connection is currently alive
'''
def __init__(self, url, user=None, domain=None, password=None, loginkey=None, proxy=None, token=None, ignore_ssl=False, auto_reconnect=False):
2024-11-01 12:00:16 -07:00
if len(url) < 5 or ((not url.startswith('wss://')) and (not url.startsWith('ws://'))):
raise ValueError("Invalid URL")
if (not url.endswith('/')):
url += '/'
url += 'control.ashx'
if (not user or (not password and not loginkey)):
raise exceptions.MeshCtrlError("No login credentials given")
if loginkey:
try:
with open(loginkey, "r") as infile:
loginkey = infile.read()
except FileNotFoundError:
pass
ckey = loginkey
try:
ckey = bytes.fromhex(loginkey)
except:
pass
if len(ckey) != 80:
raise ValueError("Invalid login key")
domainid = '',
username = 'admin'
if (domain != None):
domainid = domain
if (user != None):
username = user
url += '?auth=' + util._encode_cookie({ userid: 'user/' + domainid + '/' + username, domainid: domainid }, ckey)
2024-11-01 12:00:16 -07:00
if token:
token = b',' + base64.b64encode(token.encode())
self.url = url
self._proxy = proxy
self._user = user
self._domain = domain
self._password = password
self._token = token
self._loginkey = loginkey
self._socket_open = asyncio.Event()
self._inflight = set()
self._file_tunnels = {}
self._shell_tunnels = {}
self._smart_shell_tunnels = {}
self._ignoreSSL = ignoreSSL
self._eventer = util.Eventer()
self.initialized = asyncio.Event()
self._initialization_err = None
self._main_loop_task = asyncio.create_task(self._main_loop())
self._main_loop_error = None
self._server_info = {}
self._user_info = {}
self._command_id = 0
self.alive = False
self._message_queue = asyncio.Queue()
self._send_task = None
self._listen_task = None
async def _main_loop(self):
options = {}
if self._ignoreSSL:
options = { ssl: False }
# Setup the HTTP proxy if needed
# if (self._proxy != None):
# options.agent = new https_proxy_agent(urllib.parse(this._proxy))
headers = websockets.datastructures.Headers()
if (self._password):
token = self._token if self._token else b""
headers['x-meshauth'] = (base64.b64encode(self._user.encode()) + b',' + base64.b64encode(self._password.encode()) + token).decode()
options["additional_headers"] = headers
async for websocket in websockets.asyncio.client.connect(self.url, **options):
self.alive = True
self._socket_open.set()
try:
async with asyncio.TaskGroup() as tg:
tg.create_task(self._listen_data_task(websocket))
tg.create_task(self._send_data_task(websocket))
except* websockets.ConnectionClosed as e:
self._socket_open.clear()
if not self.auto_reconnect:
self.alive = False
raise
except* Exception as e:
self.initialized.set()
self.alive = False
self._socket_open.clear()
self._main_loop_error = e
@classmethod
async def create(cls, *args, **kwargs):
s = cls(*args, **kwargs)
await s.initialized.wait()
return s
async def _send_data_task(self, websocket):
while True:
message = await self._message_queue.get()
await websocket.send(message)
async def _listen_data_task(self, websocket):
async for message in websocket:
data = json.loads(message)
action = data.get("action", None)
if action == "close":
if data.get("cause", None) == "noauth":
raise exceptions.ServerError("Invalid Auth")
if action == "userinfo":
self._user_info = data["userinfo"]
self.initialized.set()
if action == "serverinfo":
self._currentDomain = data["serverinfo"]["domain"]
self._server_info = data["serverinfo"]
if action in ("event", "msg", "interuser"):
self._eventer.emit("server_event", data)
id = data.get("responseid", data.get("tag", None))
if id:
self._eventer.emit(id, data)
else:
# Some events don't user their response id, they just have the action. This should be fixed eventually.
# Broken commands include:
# meshes
# nodes
# getnetworkinfo
# lastconnect
# getsysinfo
# console.log(`emitting ${data.action}`)
self._eventer.emit(action, data)
def _get_command_id(self):
self._command_id = (self._command_id+1)%(2**32-1)
return self._command_id
async def close(self):
# Dunno yet
self._main_loop_task.cancel()
try:
await self._main_loop_task
except asyncio.CancelledError:
pass
async def __aenter__(self):
await self.initialized.wait()
return self
async def __aexit__(self, exc_t, exc_v, exc_tb):
await self.close()
@_check_socket
async def _send_command(self, data, name, timeout=None):
id = f"meshctrl_{name}_{self._get_command_id()}"
# This fixes a very theoretical bug with hash colisions in the case of an infinite int of requests. Now the bug will only happen if there are currently 2**32-1 of the same type of request going out at the same time
2024-11-01 12:00:16 -07:00
while id in self._inflight:
id = f"meshctrl_{name}_{self._get_command_id()}"
self._inflight.add(id)
responded = asyncio.Event()
response = None
def _(data):
self._inflight.remove(id)
nonlocal response
response = data
responded.set()
self._eventer.once(id, _)
await self._message_queue.put(json.dumps(data | {"tag": id, "responseid": id}))
await asyncio.wait_for(responded.wait(), timeout=timeout)
if isinstance(response, Exception):
raise response
return response
@_check_socket
async def _send_command_no_response_id(self, data, timeout=None):
responded = asyncio.Event()
response = None
def _(data):
nonlocal response
response = data
responded.set()
self._eventer.once(data["action"], _)
await self._message_queue.put(data | {"tag": id, "responseid": id})
await asyncio.wait_for(responded.wait(), timeout=timeout)
if isinstance(response, Exception):
raise response
return response
async def list_device_groups(self, timeout=None):
'''
Get device groups. Only returns meshes to which the logged in user has access
Args:
timeout (int): Duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of meshes
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error from server
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
2024-11-01 12:00:16 -07:00
data = await self._send_command({"action": "meshes"}, "list_device_groups", timeout)
return data["meshes"]
async def send_invite_email(self, group, email, name=None, message=None, meshid=None, timeout=None):
'''
Send an invite email for a group or mesh
Args:
group (str): Name of mesh to which to invite email
email (str): Email of user to invite
name (str): User's name. For display purposes.
message (str): Message to send to user in invite email
meshid (str): ID of mesh which to invite user. Overrides "group"
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, False otherwise
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def generate_invite_link(self, group, hours, flags=None, meshid=None, timeout=None):
'''
Generate an invite link for a group or mesh
Args:
group (str): Name of group to add
hours (int): Hours until link expires
flags (~meshctrl.constants.MeshRights): Bitwise flags for constants.MeshRights
meshid (str): ID of mesh which to invite user. Overrides "group"
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Invite link information
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def list_users(self, timeout=None):
'''
List users on server. Admin Only.
Args:
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of users
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def list_user_sessions(self, timeout=None):
'''
Get list of connected users. Admin Only.
Args:
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict] List of user sessions
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def list_user_groups(self, timeout=None):
'''
Get user groups. Admin will get all user groups, otherwise get limited user groups
Args:
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of groups
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def list_devices(self, details=False, group=None, meshid=None, timeout=None):
'''
Get devices to which the user has access.
Args:
details (bool): Get device details
group (str): Get devices from specific group by name. Overrides meshid
meshid (str): Get devices from specific group by id
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of nodes
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
def on_close(self, f):
'''
Listen for the socket to close
Args:
f (function(data: dict)): Function to call when the socket closes
'''
raise NotImplementedError()
def listen_to_events(self, f, filter=None):
'''
Listen to events from the server
Args:
f (function(data: dict)): Function to call when an event occurs
filter (dict): dict to filter events with. Only trigger for events that deep-match this dict. Use sets for "array.contains" and arrays for equality of lists.
Returns:
function: - Function used for listening. Use this to stop listening to events if you want that.
'''
raise NotImplementedError()
def stop_listening_to_events(self, f):
'''
Stop listening to server events
Args:
@param {function} Callback to stop listening with.
'''
raise NotImplementedError()
async def list_events(self, userid=None, nodeid=None, limit=None, timeout=None):
'''
List events visible to the currect user
Args:
userid (str): Filter by user. Overrides nodeid.
nodeid (str): Filter by node
limit (int): Limit to the N most recent events
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of events
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def list_login_tokens(self, timeout=None):
'''
List login tokens for current user. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of tokens
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_login_token(self, name, expire=None, timeout=None):
'''
Create login token for current user. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
name (str): Name of token
expire (int): Minutes until expiration. 0 or None for no expiration.
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Created token
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_login_token(self, names, timeout=None):
'''
Remove login token for current user. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
name (str): Name of token or token username
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: List of remaining tokens
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_user(self, name, password, randompass=False, domain=None, email=None, emailverified=False, resetpass=False, realname=None, phone=None, rights=None, timeout=None):
'''
Add a new user
Args:
name (str): username
password (str): user's starting password
randompass (bool): Generate a random password for the user. Overrides password
domain (str): Domain to which to add the user
email (str): User's email address
emailverified (bool): Pre-verify the user's email address
resetpass (bool): Force the user to reset their password on first login
realname (str): User's real name
phone (str): User's phone int
rights (~meshctrl.constants.UserRights): Bitwise mask of user's rights on the server
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def edit_user(self, userid, domain=None, email=None, emailverified=False, resetpass=False, realname=None, phone=None, rights=None, timeout=None):
'''
Edit an existing user
Args:
userid (str): Unique userid
domain (str): Domain to which to add the user
email (str): User's email address
emailverified (bool): Verify or unverify the user's email address
resetpass (bool): Force the user to reset their password on next login
realname (str): User's real name
phone (str): User's phone int
rights (~meshctrl.constants.UserRights): Bitwise mask of user's rights on the server
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_user(self, userid, timeout=None):
'''
Remove an existing user
Args:
userid (str): Unique userid
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_user_group(self, name, description=None, timeout=None):
'''
Create a new user group
Args:
name (str): Name of usergroup
description (str): Description of user group
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: New user group
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_user_group(self, groupid, timeout=None):
'''
Remove an existing user group
Args:
userid (str): Unique userid
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_users_to_user_group(self, userids, groupid, timeout=None):
'''
Add user(s) to an existing user group. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
ids (str|list[str]): Unique user id(s)
groupid (str): Group to add the given user to
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[str]: List of users that were successfully added
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_user_from_user_group(self, userid, groupid, timeout=None):
'''
Remove user from an existing user group
Args:
id (str): Unique user id
groupid (str): Group to remove the given user from
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_users_to_device(self, userids, nodeid, rights=None, timeout=None):
'''
Add a user to an existing node
Args:
userids (str|list[str]): Unique user id(s)
nodeid (str): Node to add the given user to
rights (~meshctrl.constants.MeshRights): Bitwise mask for the rights on the given mesh
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_users_from_device(self, nodeid, userids, timeout=None):
'''
Remove users from an existing node
Args:
nodeid (str): Node to remove the given users from
userids (str|list[str]): Unique user id(s)
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_device_group(self, name, description="", amtonly=False, features=0, consent=0, timeout=None):
'''
Create a new device group
Args:
name (str): Name of device group
description (str): Description of device group
amtonly (bool):
features (~meshctrl.constants.MeshFeatures): Bitwise features to enable on the group
consent (~meshctrl.constants.ConsentFlags): Bitwise consent flags to use for the group
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: New device group
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_device_group(self, meshid, isname=False, timeout=None):
'''
Remove an existing device group
Args:
meshid (str): Unique id of device group
isname (bool): treat "meshid" as a name instead of an id
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def edit_device_group(self, meshid, isname=False, name=None, description=None, flags=None, consent=None, invite_codes=None, backgroundonly=False, interactiveonly=False, timeout=None):
'''
Edit an existing device group
Args:
meshid (str): Unique id of device group
isname (bool): treat "meshid" as a name instead of an id
name (str): New name for group
description (str): New description
flags (~meshctrl.constants.MeshFeatures): Features to enable on the group
consent (~meshctrl.constants.ConsentFlags): Which consent flags to use for the group
invite_codes (list[str]): Create new invite codes
backgroundonly (bool): Flag for invite codes
interactiveonly (bool): Flag for invite codes
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def move_to_device_group(self, nodeids, meshid, isname=False, timeout=None):
'''
Move a device from one group to another
Args:
nodeids (str|list[str]): Unique node id(s)
meshid (str): Unique mesh id
isname (bool): treat "meshid" as a name instead of an id
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True on success, otherwise False
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_users_to_device_group(self, userids, meshid, isname=False, rights=0, timeout=None):
'''
Add a user to an existing mesh
Args:
userids (str|list[str]): Unique user id(s)
meshid (str): Mesh to add the given user to
isname (bool): Read meshid as a name rather than an id
rights (~meshctrl.constants.MeshRights): Bitwise mask for the rights on the given mesh
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Object showing which were added correctly and which were not, along with their result messages
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_users_from_device_group(self, userids, meshid, isname=False, timeout=None):
'''
Remove users from an existing mesh
Args:
userids (str|list[str]): Unique user id(s)
meshid (str): Mesh to add the given user to
isname (bool): Read meshid as a name rather than an id
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Object showing which were removed correctly and which were not
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def broadcast(self, message, userid=None, timeout=None):
'''
Broadcast a message to all users or a single user
Args:
message (str): Message to broadcast
userid (str): Optional user to which to send the message
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True if successful
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def device_info(self, nodeid, timeout=None):
'''
Get all info for a given device. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
nodeid (str): Unique id of desired node
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Object containing all meaningful device info
Raises:
ValueError: `Invalid device id` if device is not found
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def edit_device(self, nodeid, name=None, description=None, tags=None, icon=None, consent=None, timeout=None):
'''
Edit properties of an existing device
Args:
nodeid (str): Unique id of desired node
name (str): New name for device
description (str): New description for device
tags (str|list[str]]): New tags for device
icon (~meshctrl.constants.Icon): New icon for device
consent (~meshctrl.constants.ConsentFlags): New consent flags for device
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True if successful
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def run_command(self, nodeids, command, powershell=False, runasuser=False, runasuseronly=False, timeout=None):
'''
Run a command on any int of nodes. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
nodeids (str|list[str]): Unique ids of nodes on which to run the command
command (str): Command to run
powershell (bool): Use powershell to run command. Only available on Windows.
runasuser (bool): Attempt to run as a user instead of the root permissions given to the agent. Fall back to root if we cannot.
runasuseronly (bool): Error if we cannot run the command as the logged in user.
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Object containing mapped output of the commands by device
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def shell(self, nodeid, unique=False):
'''
Get a terminal shell on the given device
Args:
nodeid (str): Unique id of node on which to open the shell
unique (bool): True: Create a unique :py:class:`~meshctrl.shell.Shell`. Caller is responsible for cleanup. False: Use a cached :py:class:`~meshctrl.shell.Shell` if available, otherwise create and cache.
Returns:
:py:class:`~meshctrl.shell.Shell`: Newly created and initialized :py:class:`~meshctrl.shell.Shell` or cached :py:class:`~meshctrl.shell.Shell` if unique is False and a shell is currently active
'''
raise NotImplementedError()
async def smart_shell(self, nodeid, regex, unique=False):
'''
Get a smart terminal shell on the given device
Args:
nodeid (str): Unique id of node on which to open the shell
regex (regex): Regex to watch for to signify that the shell is ready for new input.
unique (bool): true: Create a unique :py:class:`~meshctrl.shell.SmartShell`. Caller is responsible for cleanup. False: Use a cached :py:class:`~meshctrl.shell.SmartShell` if available, otherwise create and cache.
Returns:
:py:class:`~meshctrl.shell.SmartShell`: Newly created and initialized :py:class:`~meshctrl.shell.SmartShell` or cached :py:class:`~meshctrl.shell.SmartShell` if unique is False and a smartshell with regex is currently active
'''
raise NotImplementedError()
async def wake_devices(self, nodeids, timeout=None):
'''
Wake up given devices
Args:
nodeids (str|list[str]): Unique ids of nodes which to wake
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True if successful
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def reset_devices(self, nodeids, timeout=None):
'''
Reset given devices
Args:
nodeids (str|list[str]): Unique ids of nodes which to reset
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True if successful
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def sleep_devices(self, nodeids, timeout=None):
'''
Sleep given devices
Args:
nodeids (str|list[str]): Unique ids of nodes which to sleep
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True if successful
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def power_off_devices(self, nodeids, timeout=None):
''' Power off given devices
Args:
nodeids (str|list[str]): Unique ids of nodes which to power off
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: True if successful
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def list_device_shares(self, nodeid, timeout=None):
'''
List device shares of given node. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
nodeid (str): Unique id of nodes of which to list shares
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
list[dict]: Array of dicts representing device shares
Raises:
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def add_device_share(self, nodeid, name, type=constants.SharingType.desktop, consent=None, start=None, end=None, duration=60*60, timeout=None):
'''
Add device share to given node. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
nodeid (str): Unique id of nodes of which to list shares
name (str): Name of guest with which to share
type (~meshctrl.constants.SharingType): Type of share thise should be
consent (~meshctrl.constants.ConsentFlags): Consent flags for share. Defaults to "notify" for your given constants.SharingType
start (int|datetime.datetime): When to start the share
end (int|datetime.datetime): When to end the share. If None, use duration instead
duration (int): Duration in seconds for share to exist
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
dict: Info about the newly created share
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def remove_device_share(self, nodeid, shareid, timeout=None):
'''
Remove a device share
Args:
nodeid (str): Unique node from which to remove the share
shareid (str): Unique share id to be removed
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: true if successful
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def device_open_url(self, nodeid, url, timeout=None):
'''
Open url in browser on device. WARNING: Non namespaced call. Calling this function again before it returns may cause unintended consequences.
Args:
nodeid (str): Unique node from which to remove the share
url (str): url to open
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: true if successful
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
Exception: `Failed to open url` if failure occurs
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def device_message(self, nodeid, message, title="MeshCentral", timeout=None):
'''
Display a message on remote device.
Args:
nodeid (str): Unique node from which to remove the share
message (str): message to display
title (str): message title
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: true if successful
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
'''
raise NotImplementedError()
async def device_toast(self, nodeids, message, title="MeshCentral", timeout=None):
'''
Popup a toast a message on remote device.
Args:
nodeids (str|list[str]): Unique node from which to remove the share
message (str): message to display
title (str): message title
timeout (int): duration in milliseconds to wait for a response before throwing an error
Returns:
bool: true if successful
Raises:
:py:class:`~meshctrl.exceptions.ServerError`: Error text from server if there is a failure
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
asyncio.TimeoutError: Command timed out
@todo This function returns true even if it fails, because the server tells us it succeeds before it actually knows, then later tells us it failed, but it's hard to find that because it looks exactly like a success.
'''
raise NotImplementedError()
def interuser(self, data, session=None, user=None):
'''
Fire off an interuser message. This is a fire and forget api, we have no way of checking if the user got the message.
Args:
data (serializable): Any sort of serializable data you want to send to the user
session (str): Direct session to send to. Use this after you have made connection with a specific user session.
user (str): Send message to all sessions of a particular user. One of these must be set.
Raises:
ValueError: Value error if neither user nor session are given.
:py:class:`~meshctrl.exceptions.SocketError`: Info about socket closure
'''
raise NotImplementedError()
async def upload(self, nodeid, source, target, unique_file_tunnel=False):
'''
Upload a stream to a device. This creates an _File and destroys it every call. If you need to upload multiple files, use {@link Session#file_explorer} instead.
Args:
nodeid (str): Unique id to upload stream to
source (ReadableStream): ReadableStream from which to read data
target (str): Path which to upload stream to on remote device
unique_file_tunnel (bool): True: Create a unique :py:class:`~meshctrl.files.Files` for this call, which will be cleaned up on return, else use cached or cache :py:class:`~meshctrl.files.Files`
Returns:
Promise<Object>: {result: bool whether upload succeeded, size: number of bytes uploaded}
'''
raise NotImplementedError()
async def upload_file(self, nodeid, filepath, target, unique_file_tunnel=False):
'''
Friendly wrapper around :py:class:`~meshctrl.session.Session.upload` to upload from a filepath. Creates a ReadableStream and calls upload.
Args:
nodeid (str): Unique id to upload file to
filepath (str): Path from which to read the data
target (str): Path which to upload file to on remote device
unique_file_tunnel (bool): True: Create a unique :py:class:`~meshctrl.files.Files` for this call, which will be cleaned up on return, else use cached or cache :py:class:`~meshctrl.files.Files`
Returns:
dict: {result: bool whether upload succeeded, size: number of bytes uploaded}
'''
raise NotImplementedError()
async def download(self, nodeid, source, target=None, unique_file_tunnel=False):
'''
Download a file from a device into a writable stream. This creates an :py:class:`~meshctrl.files.Files` and destroys it every call. If you need to upload multiple files, use :py:class:`~meshctrl.session.Session.file_explorer` instead.
Args:
nodeid (str): Unique id to download file from
source (str): Path from which to download from device
target (WritableStream): Stream to which to write data. If None, create new PassThrough stream which is both readable and writable.
unique_file_tunnel (bool): True: Create a unique :py:class:`~meshctrl.files.Files` for this call, which will be cleaned up on return, else use cached or cache :py:class:`~meshctrl.files.Files`
Returns:
WritableStream: The stream which has been downloaded into
Raises:
Exception: String showing the intermediate outcome and how many bytes were downloaded
'''
raise NotImplementedError()
async def download_file(self, nodeid, source, filepath, unique_file_tunnel=False):
'''
Friendly wrapper around :py:class:`~meshctrl.session.Session.download` to download to a filepath. Creates a WritableStream and calls download.
Args:
nodeid (str): Unique id to download file from
source (str): Path from which to download from device
filepath (str): Path to which to download data
unique_file_tunnel (bool): True: Create a unique :py:class:`~meshctrl.files.Files` for this call, which will be cleaned up on return, else use cached or cache :py:class:`~meshctrl.files.Files`
Returns:
WritableStream: The stream which has been downloaded into
'''
raise NotImplementedError()
async def file_explorer(self, nodeid, unique=False):
'''
Create, initialize, and return an :py:class:`~meshctrl.files.Files` object for the given node
Args:
nodeid (str): Unique id on which to open file explorer
unique (bool): True: Create a unique :py:class:`~meshctrl.files.Files`. Caller is responsible for cleanup. False: Use a cached :py:class:`~meshctrl.files.Files` if available, otherwise create and cache.
Returns:
:py:class:`~meshctrl.files.Files`: A newly initialized file explorer.
'''
2024-11-01 12:00:16 -07:00
raise NotImplementedError()