From f1b673dc71f73be5040e9bb1b2cc2c6bb795b1f6 Mon Sep 17 00:00:00 2001 From: aaron Date: Sat, 20 Dec 2025 06:55:55 -0500 Subject: [PATCH] Use POST for datasource control and allow fields in device list --- .gitignore | 1 + CHANGELOG.rst | 10 ++++++++-- README.rst | 36 ++++++++++++++++++++++++++++++++++++ kismet_rest/__init__.py | 2 +- kismet_rest/datasources.py | 9 ++++----- kismet_rest/devices.py | 11 ++++++++++- 6 files changed, 60 insertions(+), 9 deletions(-) diff --git a/.gitignore b/.gitignore index 7a39759..9e275d0 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,4 @@ /build/ /dist/ docs/build/ +kismet-web-docs/ diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f3040d5..a0c5675 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,6 +1,14 @@ Changelog ========= +v2025.03.13 +----------- + +Changes +~~~~~~~ +- Allow passing fields/regex to Devices.all via POST payload. +- Switch datasource control endpoints (pause/resume/open/close) to POST to match current Kismet API. + v2020.05.01 ----------- @@ -37,5 +45,3 @@ Other - Update docs. [Mike Kershaw / Dragorn] - Start extracting module. [Mike Kershaw / Dragorn] - Started repo. [Mike Kershaw / Dragorn] - - diff --git a/README.rst b/README.rst index d5b445c..a00a993 100644 --- a/README.rst +++ b/README.rst @@ -28,6 +28,42 @@ Installing from source Usage examples -------------- +Authentication and setup +~~~~~~~~~~~~~~~~~~~~~~~~ + +Kismet now supports API tokens and roles. Use an API key with the ``admin`` role for controlling datasources, or a ``readonly`` key for queries. Pass it as ``apikey`` when creating an interface (basic auth via ``username`` / ``password`` also works). + +Quickstart (modern API + API key): +~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + +:: + + from kismet_rest import Datasources, Devices + + # Replace with your Kismet host and an API key (admin role for control) + ds = Datasources(host_uri="http://127.0.0.1:2501", apikey="YOUR_ADMIN_API_KEY") + + # List datasources + for src in ds.all(): + print(src["kismet.datasource.uuid"], src["kismet.datasource.name"]) + + # Ensure a datasource is running (required before devices will appear) + target_uuid = "" + ds.open(target_uuid) # start it if it was not auto-started + ds.set_hop(target_uuid) # optional: enable hopping + + # Fetch recently active devices + dev = Devices(host_uri="http://127.0.0.1:2501", apikey="YOUR_READONLY_OR_ADMIN_KEY") + for device in dev.all(ts=0, fields=["kismet.device.base.macaddr"]): + print(device.get("kismet.device.base.macaddr")) + +Notes and troubleshooting +~~~~~~~~~~~~~~~~~~~~~~~~~ + +- Datasource control endpoints (pause/resume/close/open) require POST and the admin role in current Kismet releases. +- If no devices are returned, make sure at least one datasource is open/running; either configure it to auto-start in ``kismet.conf`` or call ``open(uuid)`` via the API. +- Device listing supports field simplification and regex filters; use the ``fields`` and ``regex`` kwargs with ``Devices.all`` to reduce response size. +- Tested against Kismet 2025-09-R1. Legacy functionality (KismetConnector): ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/kismet_rest/__init__.py b/kismet_rest/__init__.py index 46b0e5a..04b4277 100644 --- a/kismet_rest/__init__.py +++ b/kismet_rest/__init__.py @@ -22,4 +22,4 @@ from .system import System # NOQA from .utility import Utility # NOQA -__version__ = "2023.01.01" +__version__ = "2025.03.13" diff --git a/kismet_rest/datasources.py b/kismet_rest/datasources.py index 01335dd..a683740 100644 --- a/kismet_rest/datasources.py +++ b/kismet_rest/datasources.py @@ -125,7 +125,7 @@ def pause(self, source): """ url = "/datasource/by-uuid/{}/pause_source.cmd".format(source) - return self.interact("GET", url, only_status=True) + return self.interact("POST", url, only_status=True) def resume(self, source): """Resume paused source. @@ -138,7 +138,7 @@ def resume(self, source): """ url = "/datasource/by-uuid/{}/resume_source.cmd".format(source) - return self.interact("GET", url, only_status=True) + return self.interact("POST", url, only_status=True) def close(self, uuid): """Close source. A closed source will no longer be processed, and will remain closed unless reopened. @@ -151,7 +151,7 @@ def close(self, uuid): """ url = "/datasource/by-uuid/{}/close_source.cmd".format(uuid) - return self.interact("GET", url, only_status=True) + return self.interact("POST", url, only_status=True) def open(self, uuid): """Reopen a closed source. @@ -164,5 +164,4 @@ def open(self, uuid): """ url = "/datasource/by-uuid/{}/open_source.cmd".format(uuid) - return self.interact("GET", url, only_status=True) - + return self.interact("POST", url, only_status=True) diff --git a/kismet_rest/devices.py b/kismet_rest/devices.py index 2acb47e..9e69737 100644 --- a/kismet_rest/devices.py +++ b/kismet_rest/devices.py @@ -20,6 +20,8 @@ def all(self, callback=None, callback_args=None, **kwargs): Keyword args: ts (int): Starting last-seen timestamp in seconds since Epoch. + fields (list): List of fields to return. + regex (list): Regex filters per Kismet command_param spec. Yield: dict: Device json, or None if callback is set. @@ -29,10 +31,17 @@ def all(self, callback=None, callback_args=None, **kwargs): callback_settings["callback"] = callback if callback_args: callback_settings["callback_args"] = callback_args + valid_payload = ["fields", "regex"] + payload = {kword: kwargs[kword] for kword in valid_payload + if kword in kwargs} + # Remove payload-only keys so they don't get formatted into the URL. + for kword in valid_payload: + kwargs.pop(kword, None) query_args = self.kwargs_defaults.copy() query_args.update(kwargs) url = self.url_template.format(**query_args) - for result in self.interact_yield("POST", url, **callback_settings): + for result in self.interact_yield("POST", url, payload=payload, + **callback_settings): yield result def by_mac(self, callback=None, callback_args=None, **kwargs):