Files
PrimAITE/tests/integration_tests/component_creation/test_permission_system.py

194 lines
7.2 KiB
Python
Raw Permalink Normal View History

2025-01-02 15:05:06 +00:00
# © Crown-owned copyright 2025, Defence Science and Technology Laboratory UK
2023-08-03 16:26:33 +01:00
from enum import Enum
from typing import Dict, List, Literal
import pytest
2023-10-09 13:24:08 +01:00
from primaite.simulator.core import AllowAllValidator, RequestManager, RequestType, SimComponent
2023-08-03 16:26:33 +01:00
from primaite.simulator.domain.controller import AccountGroup, GroupMembershipValidator
2023-09-19 16:11:42 +01:00
@pytest.mark.skip(reason="Action validation is not currently a required feature.")
2023-08-03 16:26:33 +01:00
def test_group_action_validation() -> None:
2023-08-07 10:55:29 +01:00
"""
Check that actions are denied when an unauthorised request is made.
This test checks the integration between SimComponent and the permissions validation system. First, we create a
basic node and folder class. We configure the node so that only admins can create a folder. Then, we try to create
a folder as both an admin user and a non-admin user.
"""
2023-08-03 16:26:33 +01:00
class Folder(SimComponent):
name: str
def describe_state(self) -> Dict:
return super().describe_state()
class Node(SimComponent):
name: str
folders: List[Folder] = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
2023-10-09 13:24:08 +01:00
self._request_manager = RequestManager()
2023-08-03 16:26:33 +01:00
2023-10-09 13:24:08 +01:00
self._request_manager.add_request(
2023-08-03 16:26:33 +01:00
"create_folder",
2023-10-09 13:24:08 +01:00
RequestType(
2023-08-03 16:26:33 +01:00
func=lambda request, context: self.create_folder(request[0]),
2023-08-09 12:06:06 +01:00
validator=GroupMembershipValidator([AccountGroup.LOCAL_ADMIN, AccountGroup.DOMAIN_ADMIN]),
2023-08-03 16:26:33 +01:00
),
)
def describe_state(self) -> Dict:
return super().describe_state()
def create_folder(self, folder_name: str) -> None:
new_folder = Folder(uuid="0000-0000-0001", name=folder_name)
self.folders.append(new_folder)
def remove_folder(self, folder: Folder) -> None:
self.folders = [x for x in self.folders if x is not folder]
2023-08-07 10:55:29 +01:00
# check that the folder is created when a local admin tried to do it
2023-08-09 12:06:06 +01:00
permitted_context = {"request_source": {"agent": "BLUE", "account": "User1", "groups": ["LOCAL_ADMIN"]}}
2023-08-03 16:26:33 +01:00
my_node = Node(uuid="0000-0000-1234", name="pc")
2023-10-09 13:24:08 +01:00
my_node.apply_request(["create_folder", "memes"], context=permitted_context)
2023-08-03 16:26:33 +01:00
assert len(my_node.folders) == 1
assert my_node.folders[0].name == "memes"
2023-08-07 10:55:29 +01:00
# check that the number of folders is still 1 even after attempting to create a second one without permissions
2023-08-09 12:06:06 +01:00
invalid_context = {"request_source": {"agent": "BLUE", "account": "User1", "groups": ["LOCAL_USER", "DOMAIN_USER"]}}
2023-10-09 13:24:08 +01:00
my_node.apply_request(["create_folder", "memes2"], context=invalid_context)
2023-08-03 16:26:33 +01:00
assert len(my_node.folders) == 1
assert my_node.folders[0].name == "memes"
2023-09-19 16:11:42 +01:00
@pytest.mark.skip(reason="Action validation is not currently a required feature.")
2023-08-03 16:26:33 +01:00
def test_hierarchical_action_with_validation() -> None:
2023-08-07 10:55:29 +01:00
"""
Check that validation works with sub-objects.
This test creates a parent object (Node) and a child object (Application) which both accept actions. The node allows
action passthrough to applications. The purpose of this test is to check that after an action is passed through to
a child object, that the permission system still works as intended.
"""
2023-08-03 16:26:33 +01:00
class Application(SimComponent):
name: str
state: Literal["on", "off", "disabled"] = "off"
def __init__(self, **kwargs):
super().__init__(**kwargs)
2023-10-09 13:24:08 +01:00
self.request_manager = RequestManager()
2023-08-03 16:26:33 +01:00
2023-10-09 13:24:08 +01:00
self.request_manager.add_request(
2023-08-03 16:26:33 +01:00
"turn_on",
2023-10-09 13:24:08 +01:00
RequestType(
2023-08-03 16:26:33 +01:00
func=lambda request, context: self.turn_on(),
validator=AllowAllValidator(),
),
)
2023-10-09 13:24:08 +01:00
self.request_manager.add_request(
2023-08-03 16:26:33 +01:00
"turn_off",
2023-10-09 13:24:08 +01:00
RequestType(
2023-08-03 16:26:33 +01:00
func=lambda request, context: self.turn_off(),
validator=AllowAllValidator(),
),
)
2023-10-09 13:24:08 +01:00
self.request_manager.add_request(
2023-08-03 16:26:33 +01:00
"disable",
2023-10-09 13:24:08 +01:00
RequestType(
2023-08-03 16:26:33 +01:00
func=lambda request, context: self.disable(),
2023-08-09 12:06:06 +01:00
validator=GroupMembershipValidator([AccountGroup.LOCAL_ADMIN, AccountGroup.DOMAIN_ADMIN]),
2023-08-03 16:26:33 +01:00
),
)
2023-10-09 13:24:08 +01:00
self.request_manager.add_request(
2023-08-03 16:26:33 +01:00
"enable",
2023-10-09 13:24:08 +01:00
RequestType(
2023-08-03 16:26:33 +01:00
func=lambda request, context: self.enable(),
2023-08-09 12:06:06 +01:00
validator=GroupMembershipValidator([AccountGroup.LOCAL_ADMIN, AccountGroup.DOMAIN_ADMIN]),
2023-08-03 16:26:33 +01:00
),
)
def describe_state(self) -> Dict:
return super().describe_state()
def disable(self) -> None:
2023-08-07 10:55:29 +01:00
self.state = "disabled"
2023-08-03 16:26:33 +01:00
def enable(self) -> None:
2023-08-07 10:55:29 +01:00
if self.state == "disabled":
self.state = "off"
2023-08-03 16:26:33 +01:00
def turn_on(self) -> None:
2023-08-07 10:55:29 +01:00
if self.state == "off":
self.state = "on"
2023-08-03 16:26:33 +01:00
def turn_off(self) -> None:
2023-08-07 10:55:29 +01:00
if self.state == "on":
self.state = "off"
2023-08-03 16:26:33 +01:00
class Node(SimComponent):
name: str
state: Literal["on", "off"] = "on"
apps: List[Application] = []
def __init__(self, **kwargs):
super().__init__(**kwargs)
2023-10-09 13:24:08 +01:00
self.request_manager = RequestManager()
2023-08-03 16:26:33 +01:00
2023-10-09 13:24:08 +01:00
self.request_manager.add_request(
2023-08-03 16:26:33 +01:00
"apps",
2023-10-09 13:24:08 +01:00
RequestType(
2023-08-03 16:26:33 +01:00
func=lambda request, context: self.send_action_to_app(request.pop(0), request, context),
validator=AllowAllValidator(),
),
)
def describe_state(self) -> Dict:
return super().describe_state()
def install_app(self, app_name: str) -> None:
new_app = Application(name=app_name)
self.apps.append(new_app)
def send_action_to_app(self, app_name: str, options: List[str], context: Dict):
for app in self.apps:
if app_name == app.name:
2023-10-09 13:24:08 +01:00
app.apply_request(options, context)
2023-08-03 16:26:33 +01:00
break
else:
msg = f"Node has no app with name {app_name}"
raise LookupError(msg)
my_node = Node(name="pc")
my_node.install_app("Chrome")
my_node.install_app("Firefox")
non_admin_context = {
2023-08-09 12:06:06 +01:00
"request_source": {"agent": "BLUE", "account": "User1", "groups": ["LOCAL_USER", "DOMAIN_USER"]}
2023-08-03 16:26:33 +01:00
}
admin_context = {
"request_source": {
"agent": "BLUE",
"account": "User1",
2023-08-09 12:06:06 +01:00
"groups": ["LOCAL_ADMIN", "DOMAIN_ADMIN", "LOCAL_USER", "DOMAIN_USER"],
2023-08-03 16:26:33 +01:00
}
}
2023-08-07 10:55:29 +01:00
# check that a non-admin can't disable this app
2023-10-09 13:24:08 +01:00
my_node.apply_request(["apps", "Chrome", "disable"], non_admin_context)
2023-08-07 10:55:29 +01:00
assert my_node.apps[0].name == "Chrome" # if failure occurs on this line, the test itself is broken
assert my_node.apps[0].state == "off"
# check that a non-admin can turn this app on
2023-10-09 13:24:08 +01:00
my_node.apply_request(["apps", "Firefox", "turn_on"], non_admin_context)
2023-08-07 10:55:29 +01:00
assert my_node.apps[1].name == "Firefox" # if failure occurs on this line, the test itself is broken
assert my_node.apps[1].state == "on"
2023-08-03 16:26:33 +01:00
2023-08-07 10:55:29 +01:00
# check that an admin can disable this app
2023-10-09 13:24:08 +01:00
my_node.apply_request(["apps", "Chrome", "disable"], admin_context)
2023-08-07 10:55:29 +01:00
assert my_node.apps[0].state == "disabled"