-
Notifications
You must be signed in to change notification settings - Fork 8
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implement a basic user session management (#104)
This patch introduces a basic user session management that relies on the /etc/machine-id and the effective user id
- Loading branch information
Showing
4 changed files
with
178 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,71 @@ | ||
"""Session management module for the daemon.""" | ||
|
||
import logging | ||
import uuid | ||
from pathlib import Path | ||
from typing import Optional | ||
|
||
#: Path to the machine ID file | ||
MACHINE_ID_PATH: Path = Path("/etc/machine-id") | ||
|
||
logger = logging.getLogger(__name__) | ||
|
||
|
||
class UserSessionManager: | ||
"""Manage user session information.""" | ||
|
||
def __init__(self, effective_user: str) -> None: | ||
"""Initialize the session manager. | ||
Args: | ||
effective_user (str): The effective user id | ||
""" | ||
self._machine_uuid: Optional[uuid.UUID] = None | ||
self._effective_user: str = effective_user | ||
self._session_uuid: Optional[uuid.UUID] = None | ||
|
||
@property | ||
def machine_id(self) -> uuid.UUID: | ||
"""Property that holds the machine UUID. | ||
Reference: | ||
https://www.freedesktop.org/software/systemd/man/latest/machine-id.html | ||
Raises: | ||
FileNotFoundError: If the machine-id file doesn't exist | ||
ValueError: If the machine-id file is empty or malformed | ||
Returns: | ||
uuid.UUID: The UUID generated from machine-id | ||
""" | ||
if not self._machine_uuid: | ||
try: | ||
machine_id = MACHINE_ID_PATH.read_text().strip() | ||
if not machine_id: | ||
logger.error("Machine ID file is empty") | ||
raise ValueError(f"Machine ID at {MACHINE_ID_PATH} is empty") | ||
# Create a UUID from the machine-id string | ||
self._machine_uuid = uuid.UUID(machine_id) | ||
except FileNotFoundError as e: | ||
logger.error("Machine ID file not found at %s", MACHINE_ID_PATH) | ||
raise FileNotFoundError( | ||
f"Machine ID file not found at {MACHINE_ID_PATH}" | ||
) from e | ||
|
||
return self._machine_uuid | ||
|
||
@property | ||
def session_id(self) -> uuid.UUID: | ||
"""Property that generates a unique session ID combining machine and user effective id. | ||
Returns: | ||
uuid.UUID: A unique session identifier | ||
""" | ||
if not self._session_uuid: | ||
# Combine machine ID and effective user to create a unique namespace | ||
namespace = self.machine_id | ||
|
||
# Generate a UUID using the effective username as name in the namespace | ||
self._session_uuid = uuid.uuid5(namespace, self._effective_user) | ||
|
||
return self._session_uuid |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,8 @@ | ||
Command Line Assistant Daemon | ||
============================= | ||
|
||
.. automodule:: command_line_assistant.daemon.clad | ||
:members: | ||
:undoc-members: | ||
:private-members: | ||
:no-index: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,96 @@ | ||
import uuid | ||
from unittest.mock import patch | ||
|
||
import pytest | ||
|
||
from command_line_assistant.daemon.session import UserSessionManager | ||
|
||
|
||
def test_initialize_user_session_manager(): | ||
session = UserSessionManager("1000") | ||
assert session._effective_user == "1000" | ||
assert not session._machine_uuid | ||
assert not session._session_uuid | ||
|
||
|
||
def test_read_machine_id(tmp_path): | ||
machine_id = tmp_path / "machine-id" | ||
machine_id.write_text("09e28913cb074ed995a239c93b07fd8a") | ||
with patch("command_line_assistant.daemon.session.MACHINE_ID_PATH", machine_id): | ||
session = UserSessionManager("1000") | ||
assert session.machine_id == uuid.UUID("09e28913cb074ed995a239c93b07fd8a") | ||
|
||
|
||
def test_generate_session_id(tmp_path): | ||
machine_id = tmp_path / "machine-id" | ||
machine_id.write_text("09e28913cb074ed995a239c93b07fd8a") | ||
with patch("command_line_assistant.daemon.session.MACHINE_ID_PATH", machine_id): | ||
session = UserSessionManager("1000") | ||
assert session.session_id == uuid.UUID("4d465f1c-0507-5dfa-9ea0-e2de1a9e90a5") | ||
|
||
|
||
def test_generate_session_id_twice(tmp_path): | ||
"""This verifies that the session ID is generated only once.""" | ||
machine_id = tmp_path / "machine-id" | ||
machine_id.write_text("09e28913cb074ed995a239c93b07fd8a") | ||
with patch("command_line_assistant.daemon.session.MACHINE_ID_PATH", machine_id): | ||
session = UserSessionManager("1000") | ||
assert session.session_id == uuid.UUID("4d465f1c-0507-5dfa-9ea0-e2de1a9e90a5") | ||
|
||
session = UserSessionManager("1000") | ||
assert session.session_id == uuid.UUID("4d465f1c-0507-5dfa-9ea0-e2de1a9e90a5") | ||
|
||
|
||
@pytest.mark.parametrize( | ||
("machine_id", "effective_user_id", "expected"), | ||
( | ||
( | ||
"09e28913cb074ed995a239c93b07fd8a", | ||
"1000", | ||
"4d465f1c-0507-5dfa-9ea0-e2de1a9e90a5", | ||
), | ||
# Different user on the same machine. | ||
( | ||
"09e28913cb074ed995a239c93b07fd8a", | ||
"1001", | ||
"9f522470-d57d-55e2-8f74-b90b19830e9d", | ||
), | ||
# Same effective user id, but in a different machine | ||
( | ||
"771640198a6344bba7ad356cf525243a", | ||
"1000", | ||
"b4e5eb85-c750-5130-bd72-851e531e73c3", | ||
), | ||
), | ||
) | ||
def test_generate_session_id_different_users( | ||
tmp_path, machine_id, effective_user_id, expected | ||
): | ||
machine_id_file = tmp_path / "machine-id" | ||
machine_id_file.write_text(machine_id) | ||
with patch( | ||
"command_line_assistant.daemon.session.MACHINE_ID_PATH", machine_id_file | ||
): | ||
session = UserSessionManager(effective_user_id) | ||
assert session.session_id == uuid.UUID(expected) | ||
|
||
|
||
def test_empty_machine_id_file(tmp_path): | ||
machine_id_file = tmp_path / "machine-id" | ||
machine_id_file.write_text("") | ||
with patch( | ||
"command_line_assistant.daemon.session.MACHINE_ID_PATH", machine_id_file | ||
): | ||
session = UserSessionManager("1000") | ||
with pytest.raises(ValueError, match="Machine ID at .* is empty"): | ||
assert session.machine_id | ||
|
||
|
||
def test_machine_id_file_not_found(tmp_path): | ||
machine_id_file = tmp_path / "test" / "machine-id" | ||
with patch( | ||
"command_line_assistant.daemon.session.MACHINE_ID_PATH", machine_id_file | ||
): | ||
session = UserSessionManager("1000") | ||
with pytest.raises(FileNotFoundError, match="Machine ID file not found at .*"): | ||
assert session.machine_id |