Skip to content

Commit 256a801

Browse files
authored
feat: Add session ID file (#279)
* feat: Add session ID file * Add support for differentiating between tokens and session IDs in logout * Remove `clear_auth_token_file` command * Add `SessionID{File,List,Info}` tests * test: Update `add_zabbix_endpoint` comments, order * Rename: "Session ID file" -> "Session file" * rename default session file * docs: Update auth + config options * Update changelog * Fix `SessionFile` subclass inheritance * Add INFO log for successful session file save * Simplify `SessionFile` API * Docs: Add explainer on session file purpose
1 parent 675869c commit 256a801

16 files changed

+733
-122
lines changed

Diff for: CHANGELOG

+9
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,20 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1212
### Added
1313

1414
- Environment variable `ZABBIX_URL` to specify the URL for the Zabbix API.
15+
- Session file for storing Zabbix API sessions for multiple URLs and users.
16+
- This allows for multiple Zabbix instances to be used without re-authenticating.
17+
- The session file is stored in the application's data directory by default with the name `.zabbix-cli_session.json`.
18+
- `app.use_session_file` configuration option to enable or disable session file usage.
1519

1620
### Changed
1721

1822
- Authentication info from environment variables now take priority over the configuration file.
1923

24+
### Deprecated
25+
26+
- Auth token file. Use the new session file instead. Session files are now created by default if `app.use_auth_token_file` is set to `true` in the configuration file.
27+
- `app.use_auth_token_file` configuration option. Use `app.use_session_file` instead.
28+
2029
## [3.4.2](https://github.com/unioslo/zabbix-cli/releases/tag/3.4.2) - 2024-12-16
2130

2231
### Changed

Diff for: docs/guide/authentication.md

+22-23
Original file line numberDiff line numberDiff line change
@@ -2,27 +2,24 @@
22

33
Zabbix-cli provides several ways to authenticate. They are tried in the following order:
44

5-
1. [Token - Environment variables](#environment-variables)
6-
1. [Token - Config file](#config-file)
7-
1. [Token - Auth token file](#auth-token-file)
5+
1. [API Token - Environment variables](#environment-variables)
6+
1. [API Token - Config file](#config-file)
7+
1. [Session file](#session-file)
88
1. [Password - Environment variables](#environment-variables_1)
99
1. [Password - Config file](#config-file_1)
1010
1. [Password - Auth file](#auth-file)
1111
1. [Password - Prompt](#prompt)
1212

13-
## Token
13+
## API Token
1414

15-
The application supports authenticating with an API or session token. API tokens are created in the Zabbix frontend or via `zabbix-cli create_token`. A session token is obtained by logging in to the Zabbix API with a username and password.
16-
17-
!!! info "Session vs API token"
18-
Semantically, a session token and API token are the same thing from an API authentication perspective. They are both sent as the `auth` parameter in the Zabbix API requests.
15+
The application supports authenticating with an API token. API tokens are created in the Zabbix frontend or via `zabbix-cli create_token`.
1916

2017
### Environment variables
2118

2219
The API token can be set as an environment variable:
2320

2421
```bash
25-
export ZABBIX_API_TOKEN="API TOKEN"
22+
export ZABBIX_API_TOKEN="API_TOKEN"
2623
```
2724

2825
### Config file
@@ -34,19 +31,21 @@ The token can be set directly in the config file:
3431
auth_token = "API_TOKEN"
3532
```
3633

37-
### Auth token file
34+
## Session file
35+
36+
The application can store and reuse session tokens between runs. Multiple sessions can be stored at the same time, which allows for switching between different users and/or Zabbix servers seamlessly without having to re-authenticate.
3837

39-
The application can store and reuse session tokens between runs. This feature is enabled by default and configurable via the following options:
38+
This feature is enabled by default and configurable via the following options:
4039

4140
```toml
4241
[app]
43-
# Enable token file storage (default: true)
44-
use_auth_token_file = true
42+
# Enable persistent sessions (default: true)
43+
use_session_file = true
4544

4645
# Customize token file location (optional)
47-
auth_token_file = "/path/to/auth/token/file"
46+
session_file = "/path/to/auth/token/file"
4847

49-
# Enforce secure file permissions (default: true, no effect on Windows)
48+
# Enforce secure file permissions (600) (default: true, no effect on Windows)
5049
allow_insecure_auth_file = false
5150
```
5251

@@ -117,6 +116,14 @@ They are processed in the following order:
117116

118117
The URL should not include `/api_jsonrpc.php`.
119118

119+
### Environment variables
120+
121+
The URL can also be set as an environment variable:
122+
123+
```bash
124+
export ZABBIX_URL="http://zabbix.example.com"
125+
```
126+
120127
### Config file
121128

122129
The URL of the Zabbix API can be set in the config file:
@@ -127,14 +134,6 @@ The URL of the Zabbix API can be set in the config file:
127134
url = "http://zabbix.example.com"
128135
```
129136

130-
### Environment variables
131-
132-
The URL can also be set as an environment variable:
133-
134-
```bash
135-
export ZABBIX_URL="http://zabbix.example.com"
136-
```
137-
138137
### Prompt
139138

140139
When all other methods fail, the application will prompt for the URL of the Zabbix API.

Diff for: docs/guide/configuration.md

+10-10
Original file line numberDiff line numberDiff line change
@@ -183,8 +183,8 @@ To do this, we need to add Field() for every field in the model, and also ensure
183183
export_directory = "/path/to/exports"
184184
export_format = "json"
185185
export_timestamps = true
186-
use_auth_token_file = true
187-
auth_token_file = "/path/to/auth_token_file"
186+
use_session_file = true
187+
session_file = "/path/to/session_file.json"
188188
auth_file = "/path/to/auth_token_file"
189189
history = true
190190
history_file = "/path/to/history_file.history"
@@ -304,39 +304,39 @@ To do this, we need to add Field() for every field in the model, and also ensure
304304

305305
----
306306

307-
#### `use_auth_token_file`
307+
#### `use_session_file`
308308

309-
Whether to use an auth token file to save session token once authenticated. Allows for reusing the token in subsequent sessions.
309+
Whether to save session tokens to a file for persistent user sessions.
310310

311311
Type: `bool`
312312

313313
Default: `true`
314314

315315
```toml
316316
[app]
317-
use_auth_token_file = true
317+
use_session_file = true
318318
```
319319

320320
----
321321

322-
#### `auth_token_file`
322+
#### `session_file`
323323

324-
Paht to the auth token file.
324+
Path to the session file.
325325

326326
Type: `str`
327327

328-
Default: `"<DATA_DIR>/zabbix-cli/.zabbix-cli_auth_token"`
328+
Default: `"<DATA_DIR>/zabbix-cli/.zabbix-cli_session.json"`
329329

330330
```toml
331331
[app]
332-
auth_token_file = "/path/to/auth_token_file"
332+
session_file = "/path/to/session_file.json"
333333
```
334334

335335
----
336336

337337
#### `auth_file`
338338

339-
Paht to a file containing username and password in the format `username:password`. Alternative to specifying `username` and `password` in the configuration file.
339+
Path to a file containing username and password in the format `username:password`. Alternative to specifying `username` and `password` in the configuration file.
340340

341341
Type: `str`
342342

Diff for: tests/conftest.py

-13
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,6 @@
88
import pytest
99
import typer
1010
from packaging.version import Version
11-
from pytest_httpserver import HTTPServer
1211
from typer.testing import CliRunner
1312
from zabbix_cli.app import StatefulApp
1413
from zabbix_cli.config.model import Config
@@ -113,18 +112,6 @@ def zabbix_client_mock_version(
113112
yield zabbix_client
114113

115114

116-
def add_httpserver_version_endpoint(
117-
httpserver: HTTPServer, version: Version, id: int = 0
118-
) -> None:
119-
"""Add an endpoint emulating the Zabbix apiiinfo.version method."""
120-
httpserver.expect_oneshot_request(
121-
"/api_jsonrpc.php",
122-
json={"jsonrpc": "2.0", "method": "apiinfo.version", "params": {}, "id": id},
123-
method="POST",
124-
headers={"Content-Type": "application/json-rpc"},
125-
).respond_with_json({"jsonrpc": "2.0", "result": str(version), "id": id})
126-
127-
128115
@pytest.fixture(name="force_color")
129116
def force_color() -> Generator[Any, Any, Any]:
130117
import os

Diff for: tests/data/zabbix-cli.toml

+1-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ export_directory = "/path/to/exports"
1414
export_format = "json"
1515
export_timestamps = false
1616
use_colors = true
17-
use_auth_token_file = true
17+
use_session_file = true
1818
auth_token_file = "/path/to/auth_token_file"
1919
auth_file = "/path/to/auth_file"
2020
use_paging = false

Diff for: tests/pyzabbix/test_client.py

+46-12
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@
1313
from zabbix_cli.pyzabbix.client import add_param
1414
from zabbix_cli.pyzabbix.client import append_param
1515

16-
from tests.conftest import add_httpserver_version_endpoint
16+
from tests.utils import add_zabbix_endpoint
17+
from tests.utils import add_zabbix_version_endpoint
1718

1819

1920
@pytest.mark.parametrize(
@@ -184,33 +185,66 @@ def test_client_auth_method(
184185
version: Version,
185186
expect_method: AuthMethod,
186187
) -> None:
188+
"""Test that the correct auth method (body/header) is used based on the Zabbix server version."""
187189
# Add endpoint for version check
188190
zabbix_client.set_url(httpserver.url_for("/api_jsonrpc.php"))
189191

190192
# Set a mock token we can use for testing
191193
zabbix_client.auth = "token123"
192194

193195
# Add endpoint that returns the parametrized version
194-
add_httpserver_version_endpoint(httpserver, version, id=0)
196+
add_zabbix_version_endpoint(httpserver, str(version), id=0)
195197

196198
assert zabbix_client.version == version
197199

198-
data = {"jsonrpc": "2.0", "method": "fake.method", "params": {}, "id": 1}
199-
headers = {}
200-
200+
headers: dict[str, str] = {}
201+
auth = None
201202
# We expect auth token to be in header on >= 6.4.0
202203
if expect_method == "header":
203204
headers["Authorization"] = f"Bearer {zabbix_client.auth}"
204205
else:
205-
data["auth"] = zabbix_client.auth
206+
auth = zabbix_client.auth
206207

207-
httpserver.expect_oneshot_request(
208-
"/api_jsonrpc.php",
209-
json=data,
208+
add_zabbix_endpoint(
209+
httpserver,
210+
method="test.method.do_stuff",
211+
params={},
212+
response="authtoken123456789",
210213
headers=headers,
211-
method="POST",
212-
).respond_with_json({"jsonrpc": "2.0", "result": "authtoken123456789", "id": 1})
214+
auth=auth,
215+
)
213216

214217
# Will fail if the auth method is not set correctly
215-
resp = zabbix_client.do_request("fake.method")
218+
resp = zabbix_client.do_request("test.method.do_stuff")
216219
assert resp.result == "authtoken123456789"
220+
221+
httpserver.check_assertions()
222+
httpserver.check_handler_errors()
223+
224+
225+
AuthType = Literal["token", "sessionid"]
226+
227+
228+
@pytest.mark.parametrize(
229+
"auth_type,auth",
230+
[
231+
pytest.param("token", "authtoken123456789", id="token"),
232+
pytest.param("token", "", id="token (empty string)"),
233+
pytest.param("sessionid", "sessionid123456789", id="sessionid"),
234+
pytest.param("sessionid", "", id="sessionid (empty string)"),
235+
],
236+
)
237+
def test_client_logout(httpserver: HTTPServer, auth_type: AuthType, auth: str) -> None:
238+
add_zabbix_version_endpoint(httpserver, "7.0.0")
239+
240+
# We only expect a logout request if we are using a sessionid and have an auth token
241+
if auth_type == "sessionid" and auth:
242+
add_zabbix_endpoint(httpserver, "user.logout", {}, True)
243+
zabbix_client = ZabbixAPI(server=httpserver.url_for("/api_jsonrpc.php"))
244+
zabbix_client.auth = auth
245+
if auth_type == "token":
246+
zabbix_client.use_api_token = True
247+
zabbix_client.logout()
248+
249+
httpserver.check_assertions()
250+
httpserver.check_handler_errors()

0 commit comments

Comments
 (0)