#2735 - added documentation for users, usermanager and usersessionmanager. Added the ability to add additional users from config and documented this. also tested additional users from config.
This commit is contained in:
@@ -53,3 +53,30 @@ The number of time steps required to occur in order for the node to cycle from `
|
|||||||
Optional. Default value is ``3``.
|
Optional. Default value is ``3``.
|
||||||
|
|
||||||
The number of time steps required to occur in order for the node to cycle from ``ON`` to ``SHUTTING_DOWN`` and then finally ``OFF``.
|
The number of time steps required to occur in order for the node to cycle from ``ON`` to ``SHUTTING_DOWN`` and then finally ``OFF``.
|
||||||
|
|
||||||
|
``users``
|
||||||
|
---------
|
||||||
|
|
||||||
|
The list of pre-existing users that are additional to the default admin user (``username=admin``, ``password=admin``).
|
||||||
|
Additional users are configured as an array nd must contain a ``username``, ``password``, and can contain an optional
|
||||||
|
boolean ``is_admin``.
|
||||||
|
|
||||||
|
Example of adding two additional users to a node:
|
||||||
|
|
||||||
|
.. code-block:: yaml
|
||||||
|
|
||||||
|
simulation:
|
||||||
|
network:
|
||||||
|
nodes:
|
||||||
|
- hostname: client_1
|
||||||
|
type: computer
|
||||||
|
ip_address: 192.168.10.11
|
||||||
|
subnet_mask: 255.255.255.0
|
||||||
|
default_gateway: 192.168.10.1
|
||||||
|
users:
|
||||||
|
- username: jane.doe
|
||||||
|
password: '1234'
|
||||||
|
is_admin: true
|
||||||
|
- username: john.doe
|
||||||
|
password: password_1
|
||||||
|
is_admin: false
|
||||||
|
|||||||
@@ -97,8 +97,8 @@ Node Behaviours/Functions
|
|||||||
- **receive_frame()**: Handles the processing of incoming network frames.
|
- **receive_frame()**: Handles the processing of incoming network frames.
|
||||||
- **apply_timestep()**: Advances the state of the node according to the simulation timestep.
|
- **apply_timestep()**: Advances the state of the node according to the simulation timestep.
|
||||||
- **power_on()**: Initiates the node, enabling all connected Network Interfaces and starting all Services and
|
- **power_on()**: Initiates the node, enabling all connected Network Interfaces and starting all Services and
|
||||||
Applications, taking into account the `start_up_duration`.
|
Applications, taking into account the ``start_up_duration``.
|
||||||
- **power_off()**: Stops the node's operations, adhering to the `shut_down_duration`.
|
- **power_off()**: Stops the node's operations, adhering to the ``shut_down_duration``.
|
||||||
- **ping()**: Sends ICMP echo requests to a specified IP address to test connectivity.
|
- **ping()**: Sends ICMP echo requests to a specified IP address to test connectivity.
|
||||||
- **has_enabled_network_interface()**: Checks if the node has any network interfaces enabled, facilitating network
|
- **has_enabled_network_interface()**: Checks if the node has any network interfaces enabled, facilitating network
|
||||||
communication.
|
communication.
|
||||||
@@ -109,3 +109,205 @@ Node Behaviours/Functions
|
|||||||
The Node class handles installation of system software, network connectivity, frame processing, system logging, and
|
The Node class handles installation of system software, network connectivity, frame processing, system logging, and
|
||||||
power states. It establishes baseline functionality while allowing subclassing to model specific node types like hosts,
|
power states. It establishes baseline functionality while allowing subclassing to model specific node types like hosts,
|
||||||
routers, firewalls etc. The flexible architecture enables composing complex network topologies.
|
routers, firewalls etc. The flexible architecture enables composing complex network topologies.
|
||||||
|
|
||||||
|
User, UserManager, and UserSessionManager
|
||||||
|
=========================================
|
||||||
|
|
||||||
|
The ``base.py`` module also includes essential classes for managing users and their sessions within the PrimAITE
|
||||||
|
simulation. These are the ``User``, ``UserManager``, and ``UserSessionManager`` classes. The base ``Node`` class comes
|
||||||
|
with ``UserManager``, and ``UserSessionManager`` classes pre-installed.
|
||||||
|
|
||||||
|
User Class
|
||||||
|
----------
|
||||||
|
|
||||||
|
The ``User`` class represents a user in the system. It includes attributes such as ``username``, ``password``,
|
||||||
|
``disabled``, and ``is_admin`` to define the user's credentials and status.
|
||||||
|
|
||||||
|
Example Usage
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Creating a user:
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
user = User(username="john_doe", password="12345")
|
||||||
|
|
||||||
|
UserManager Class
|
||||||
|
-----------------
|
||||||
|
|
||||||
|
The ``UserManager`` class handles user management tasks such as creating users, authenticating them, changing passwords,
|
||||||
|
and enabling or disabling user accounts. It maintains a dictionary of users and provides methods to manage them
|
||||||
|
effectively.
|
||||||
|
|
||||||
|
Example Usage
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Creating a ``UserManager`` instance and adding a user:
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
user_manager = UserManager()
|
||||||
|
user_manager.add_user(username="john_doe", password="12345")
|
||||||
|
|
||||||
|
Authenticating a user:
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
user = user_manager.authenticate_user(username="john_doe", password="12345")
|
||||||
|
|
||||||
|
UserSessionManager Class
|
||||||
|
------------------------
|
||||||
|
|
||||||
|
The ``UserSessionManager`` class manages user sessions, including local and remote sessions. It handles session creation,
|
||||||
|
timeouts, and provides methods for logging users in and out.
|
||||||
|
|
||||||
|
Example Usage
|
||||||
|
^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
Creating a ``UserSessionManager`` instance and logging a user in locally:
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
session_manager = UserSessionManager()
|
||||||
|
session_id = session_manager.local_login(username="john_doe", password="12345")
|
||||||
|
|
||||||
|
Logging a user out:
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
session_manager.local_logout()
|
||||||
|
|
||||||
|
Practical Examples
|
||||||
|
------------------
|
||||||
|
|
||||||
|
Below are unit tests which act as practical examples illustrating how to use the ``User``, ``UserManager``, and
|
||||||
|
``UserSessionManager`` classes within the context of a client-server network simulation.
|
||||||
|
|
||||||
|
Setting up a Client-Server Network
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
from typing import Tuple
|
||||||
|
from uuid import uuid4
|
||||||
|
|
||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.network.container import Network
|
||||||
|
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||||
|
from primaite.simulator.network.hardware.nodes.host.server import Server
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def client_server_network() -> Tuple[Computer, Server, Network]:
|
||||||
|
network = Network()
|
||||||
|
|
||||||
|
client = Computer(
|
||||||
|
hostname="client",
|
||||||
|
ip_address="192.168.1.2",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
start_up_duration=0,
|
||||||
|
)
|
||||||
|
client.power_on()
|
||||||
|
|
||||||
|
server = Server(
|
||||||
|
hostname="server",
|
||||||
|
ip_address="192.168.1.3",
|
||||||
|
subnet_mask="255.255.255.0",
|
||||||
|
default_gateway="192.168.1.1",
|
||||||
|
start_up_duration=0,
|
||||||
|
)
|
||||||
|
server.power_on()
|
||||||
|
|
||||||
|
network.connect(client.network_interface[1], server.network_interface[1])
|
||||||
|
|
||||||
|
return client, server, network
|
||||||
|
|
||||||
|
Local Login Success
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def test_local_login_success(client_server_network):
|
||||||
|
client, server, network = client_server_network
|
||||||
|
|
||||||
|
assert not client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
client.user_session_manager.local_login(username="admin", password="admin")
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
Local Login Failure
|
||||||
|
^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def test_local_login_failure(client_server_network):
|
||||||
|
client, server, network = client_server_network
|
||||||
|
|
||||||
|
assert not client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
client.user_session_manager.local_login(username="jane.doe", password="12345")
|
||||||
|
|
||||||
|
assert not client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
Adding a New User and Successful Local Login
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def test_new_user_local_login_success(client_server_network):
|
||||||
|
client, server, network = client_server_network
|
||||||
|
|
||||||
|
assert not client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
client.user_manager.add_user(username="jane.doe", password="12345")
|
||||||
|
|
||||||
|
client.user_session_manager.local_login(username="jane.doe", password="12345")
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
Clearing Previous Login on New Local Login
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def test_new_local_login_clears_previous_login(client_server_network):
|
||||||
|
client, server, network = client_server_network
|
||||||
|
|
||||||
|
assert not client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
current_session_id = client.user_session_manager.local_login(username="admin", password="admin")
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_session.user.username == "admin"
|
||||||
|
|
||||||
|
client.user_manager.add_user(username="jane.doe", password="12345")
|
||||||
|
|
||||||
|
new_session_id = client.user_session_manager.local_login(username="jane.doe", password="12345")
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_session.user.username == "jane.doe"
|
||||||
|
|
||||||
|
assert new_session_id != current_session_id
|
||||||
|
|
||||||
|
Persistent Login for the Same User
|
||||||
|
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. code-block:: python
|
||||||
|
|
||||||
|
def test_new_local_login_attempt_same_uses_persists(client_server_network):
|
||||||
|
client, server, network = client_server_network
|
||||||
|
|
||||||
|
assert not client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
current_session_id = client.user_session_manager.local_login(username="admin", password="admin")
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_session.user.username == "admin"
|
||||||
|
|
||||||
|
new_session_id = client.user_session_manager.local_login(username="admin", password="admin")
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_user_logged_in
|
||||||
|
|
||||||
|
assert client.user_session_manager.local_session.user.username == "admin"
|
||||||
|
|
||||||
|
assert new_session_id == current_session_id
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ from primaite.game.agent.scripted_agents.tap001 import TAP001
|
|||||||
from primaite.game.science import graph_has_cycle, topological_sort
|
from primaite.game.science import graph_has_cycle, topological_sort
|
||||||
from primaite.simulator import SIM_OUTPUT
|
from primaite.simulator import SIM_OUTPUT
|
||||||
from primaite.simulator.network.airspace import AirSpaceFrequency
|
from primaite.simulator.network.airspace import AirSpaceFrequency
|
||||||
from primaite.simulator.network.hardware.base import NodeOperatingState
|
from primaite.simulator.network.hardware.base import NodeOperatingState, UserManager
|
||||||
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
from primaite.simulator.network.hardware.nodes.host.computer import Computer
|
||||||
from primaite.simulator.network.hardware.nodes.host.host_node import NIC
|
from primaite.simulator.network.hardware.nodes.host.host_node import NIC
|
||||||
from primaite.simulator.network.hardware.nodes.host.server import Printer, Server
|
from primaite.simulator.network.hardware.nodes.host.server import Printer, Server
|
||||||
@@ -267,6 +267,7 @@ class PrimaiteGame:
|
|||||||
|
|
||||||
for node_cfg in nodes_cfg:
|
for node_cfg in nodes_cfg:
|
||||||
n_type = node_cfg["type"]
|
n_type = node_cfg["type"]
|
||||||
|
new_node = None
|
||||||
if n_type == "computer":
|
if n_type == "computer":
|
||||||
new_node = Computer(
|
new_node = Computer(
|
||||||
hostname=node_cfg["hostname"],
|
hostname=node_cfg["hostname"],
|
||||||
@@ -316,6 +317,11 @@ class PrimaiteGame:
|
|||||||
msg = f"invalid node type {n_type} in config"
|
msg = f"invalid node type {n_type} in config"
|
||||||
_LOGGER.error(msg)
|
_LOGGER.error(msg)
|
||||||
raise ValueError(msg)
|
raise ValueError(msg)
|
||||||
|
|
||||||
|
if "users" in node_cfg and new_node.software_manager.software.get("UserManager"):
|
||||||
|
user_manager: UserManager = new_node.software_manager.software["UserManager"] # noqa
|
||||||
|
for user_cfg in node_cfg["users"]:
|
||||||
|
user_manager.add_user(**user_cfg, bypass_can_perform_action=True)
|
||||||
if "services" in node_cfg:
|
if "services" in node_cfg:
|
||||||
for service_cfg in node_cfg["services"]:
|
for service_cfg in node_cfg["services"]:
|
||||||
new_service = None
|
new_service = None
|
||||||
|
|||||||
34
tests/assets/configs/basic_node_with_users.yaml
Normal file
34
tests/assets/configs/basic_node_with_users.yaml
Normal file
@@ -0,0 +1,34 @@
|
|||||||
|
io_settings:
|
||||||
|
save_step_metadata: false
|
||||||
|
save_pcap_logs: true
|
||||||
|
save_sys_logs: true
|
||||||
|
sys_log_level: WARNING
|
||||||
|
agent_log_level: INFO
|
||||||
|
save_agent_logs: true
|
||||||
|
write_agent_log_to_terminal: True
|
||||||
|
|
||||||
|
|
||||||
|
game:
|
||||||
|
max_episode_length: 256
|
||||||
|
ports:
|
||||||
|
- ARP
|
||||||
|
protocols:
|
||||||
|
- ICMP
|
||||||
|
- UDP
|
||||||
|
|
||||||
|
|
||||||
|
simulation:
|
||||||
|
network:
|
||||||
|
nodes:
|
||||||
|
- hostname: client_1
|
||||||
|
type: computer
|
||||||
|
ip_address: 192.168.10.11
|
||||||
|
subnet_mask: 255.255.255.0
|
||||||
|
default_gateway: 192.168.10.1
|
||||||
|
users:
|
||||||
|
- username: jane.doe
|
||||||
|
password: '1234'
|
||||||
|
is_admin: true
|
||||||
|
- username: john.doe
|
||||||
|
password: password_1
|
||||||
|
is_admin: false
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
# © Crown-owned copyright 2024, Defence Science and Technology Laboratory UK
|
||||||
|
import yaml
|
||||||
|
|
||||||
|
from primaite.game.game import PrimaiteGame
|
||||||
|
from primaite.simulator.network.hardware.base import UserManager
|
||||||
|
from tests import TEST_ASSETS_ROOT
|
||||||
|
|
||||||
|
|
||||||
|
def test_users_from_config():
|
||||||
|
config_path = TEST_ASSETS_ROOT / "configs" / "basic_node_with_users.yaml"
|
||||||
|
|
||||||
|
with open(config_path, "r") as f:
|
||||||
|
config_dict = yaml.safe_load(f)
|
||||||
|
network = PrimaiteGame.from_config(cfg=config_dict).simulation.network
|
||||||
|
|
||||||
|
client_1 = network.get_node_by_hostname("client_1")
|
||||||
|
|
||||||
|
user_manager: UserManager = client_1.software_manager.software["UserManager"]
|
||||||
|
|
||||||
|
assert len(user_manager.users) == 3
|
||||||
|
|
||||||
|
assert user_manager.users["jane.doe"].password == "1234"
|
||||||
|
assert user_manager.users["jane.doe"].is_admin
|
||||||
|
|
||||||
|
assert user_manager.users["john.doe"].password == "password_1"
|
||||||
|
assert not user_manager.users["john.doe"].is_admin
|
||||||
Reference in New Issue
Block a user