#1943: unit tests + refactoring HTTP -> Http
This commit is contained in:
@@ -25,8 +25,8 @@ Usage
|
|||||||
Implementation
|
Implementation
|
||||||
^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
- HTTP request uses a ``HTTPRequestPacket`` object
|
- HTTP request uses a ``HttpRequestPacket`` object
|
||||||
- HTTP reaponse uses a ``HTTPResponsePacket`` object
|
- HTTP response uses a ``HttpResponsePacket`` object
|
||||||
- Extends Service class for integration with ``SoftwareManager``.
|
- Extends Service class for integration with ``SoftwareManager``.
|
||||||
|
|
||||||
Web Browser (Web Client)
|
Web Browser (Web Client)
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ from primaite.simulator.system.applications.database_client import DatabaseClien
|
|||||||
from primaite.simulator.system.services.database.database_service import DatabaseService
|
from primaite.simulator.system.services.database.database_service import DatabaseService
|
||||||
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
from primaite.simulator.system.services.dns.dns_server import DNSServer
|
||||||
from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot
|
from primaite.simulator.system.services.red_services.data_manipulation_bot import DataManipulationBot
|
||||||
from primaite.simulator.system.services.web_server.web_server_service import WebServer
|
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||||
|
|
||||||
|
|
||||||
def client_server_routed() -> Network:
|
def client_server_routed() -> Network:
|
||||||
|
|||||||
@@ -3,7 +3,7 @@ from enum import Enum
|
|||||||
from primaite.simulator.network.protocols.packet import DataPacket
|
from primaite.simulator.network.protocols.packet import DataPacket
|
||||||
|
|
||||||
|
|
||||||
class HTTPRequestMethod(Enum):
|
class HttpRequestMethod(Enum):
|
||||||
"""Enum list of HTTP Request methods that can be handled by the simulation."""
|
"""Enum list of HTTP Request methods that can be handled by the simulation."""
|
||||||
|
|
||||||
GET = "GET"
|
GET = "GET"
|
||||||
@@ -25,7 +25,7 @@ class HTTPRequestMethod(Enum):
|
|||||||
"""Apply partial modifications to a resource."""
|
"""Apply partial modifications to a resource."""
|
||||||
|
|
||||||
|
|
||||||
class HTTPStatusCode(Enum):
|
class HttpStatusCode(Enum):
|
||||||
"""List of available HTTP Statuses."""
|
"""List of available HTTP Statuses."""
|
||||||
|
|
||||||
OK = 200
|
OK = 200
|
||||||
@@ -37,6 +37,9 @@ class HTTPStatusCode(Enum):
|
|||||||
UNAUTHORIZED = 401
|
UNAUTHORIZED = 401
|
||||||
"""Auth required."""
|
"""Auth required."""
|
||||||
|
|
||||||
|
NOT_FOUND = 404
|
||||||
|
"""Item not found in server."""
|
||||||
|
|
||||||
METHOD_NOT_ALLOWED = 405
|
METHOD_NOT_ALLOWED = 405
|
||||||
"""Method is not supported by server."""
|
"""Method is not supported by server."""
|
||||||
|
|
||||||
@@ -44,18 +47,18 @@ class HTTPStatusCode(Enum):
|
|||||||
"""Error on the server side."""
|
"""Error on the server side."""
|
||||||
|
|
||||||
|
|
||||||
class HTTPRequestPacket(DataPacket):
|
class HttpRequestPacket(DataPacket):
|
||||||
"""Class that represents an HTTP Request Packet."""
|
"""Class that represents an HTTP Request Packet."""
|
||||||
|
|
||||||
request_method: HTTPRequestMethod
|
request_method: HttpRequestMethod
|
||||||
"""The HTTP Request method."""
|
"""The HTTP Request method."""
|
||||||
|
|
||||||
request_url: str
|
request_url: str
|
||||||
"""URL of request."""
|
"""URL of request."""
|
||||||
|
|
||||||
|
|
||||||
class HTTPResponsePacket(DataPacket):
|
class HttpResponsePacket(DataPacket):
|
||||||
"""Class that reprensents an HTTP Response Packet."""
|
"""Class that reprensents an HTTP Response Packet."""
|
||||||
|
|
||||||
status_code: HTTPStatusCode = None
|
status_code: HttpStatusCode = None
|
||||||
"""Status code of the HTTP response."""
|
"""Status code of the HTTP response."""
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ from ipaddress import IPv4Address
|
|||||||
from typing import Dict, Optional
|
from typing import Dict, Optional
|
||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from primaite.simulator.network.protocols.http import HTTPRequestMethod, HTTPRequestPacket, HTTPResponsePacket
|
from primaite.simulator.network.protocols.http import HttpRequestMethod, HttpRequestPacket, HttpResponsePacket
|
||||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
from primaite.simulator.system.applications.application import Application
|
from primaite.simulator.system.applications.application import Application
|
||||||
@@ -19,7 +19,7 @@ class WebBrowser(Application):
|
|||||||
domain_name_ip_address: Optional[IPv4Address] = None
|
domain_name_ip_address: Optional[IPv4Address] = None
|
||||||
"The IP address of the domain name for the webpage."
|
"The IP address of the domain name for the webpage."
|
||||||
|
|
||||||
latest_response: HTTPResponsePacket = None
|
latest_response: HttpResponsePacket = None
|
||||||
"""Keeps track of the latest HTTP response."""
|
"""Keeps track of the latest HTTP response."""
|
||||||
|
|
||||||
def __init__(self, **kwargs):
|
def __init__(self, **kwargs):
|
||||||
@@ -87,7 +87,7 @@ class WebBrowser(Application):
|
|||||||
return False
|
return False
|
||||||
|
|
||||||
# create HTTPRequest payload
|
# create HTTPRequest payload
|
||||||
payload = HTTPRequestPacket(request_method=HTTPRequestMethod.GET, request_url=url)
|
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url=url)
|
||||||
|
|
||||||
# send request
|
# send request
|
||||||
return self.send(
|
return self.send(
|
||||||
@@ -98,7 +98,7 @@ class WebBrowser(Application):
|
|||||||
|
|
||||||
def send(
|
def send(
|
||||||
self,
|
self,
|
||||||
payload: HTTPRequestPacket,
|
payload: HttpRequestPacket,
|
||||||
dest_ip_address: Optional[IPv4Address] = None,
|
dest_ip_address: Optional[IPv4Address] = None,
|
||||||
dest_port: Optional[Port] = Port.HTTP,
|
dest_port: Optional[Port] = Port.HTTP,
|
||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
@@ -120,7 +120,7 @@ class WebBrowser(Application):
|
|||||||
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs
|
payload=payload, dest_ip_address=dest_ip_address, dest_port=dest_port, session_id=session_id, **kwargs
|
||||||
)
|
)
|
||||||
|
|
||||||
def receive(self, payload: HTTPResponsePacket, session_id: Optional[str] = None, **kwargs) -> bool:
|
def receive(self, payload: HttpResponsePacket, session_id: Optional[str] = None, **kwargs) -> bool:
|
||||||
"""
|
"""
|
||||||
Receives a payload from the SessionManager.
|
Receives a payload from the SessionManager.
|
||||||
|
|
||||||
@@ -128,8 +128,8 @@ class WebBrowser(Application):
|
|||||||
:param session_id: The Session ID the payload is to originate from. Optional.
|
:param session_id: The Session ID the payload is to originate from. Optional.
|
||||||
:return: True if successful, False otherwise.
|
:return: True if successful, False otherwise.
|
||||||
"""
|
"""
|
||||||
if not isinstance(payload, HTTPResponsePacket):
|
if not isinstance(payload, HttpResponsePacket):
|
||||||
self.sys_log.error(f"{self.name} received a packet that is not an HTTPResponsePacket")
|
self.sys_log.error(f"{self.name} received a packet that is not an HttpResponsePacket")
|
||||||
return False
|
return False
|
||||||
self.sys_log.info(f"{self.name}: Received HTTP {payload.status_code.value}")
|
self.sys_log.info(f"{self.name}: Received HTTP {payload.status_code.value}")
|
||||||
self.latest_response = payload
|
self.latest_response = payload
|
||||||
|
|||||||
@@ -3,10 +3,10 @@ from typing import Any, Optional
|
|||||||
from urllib.parse import urlparse
|
from urllib.parse import urlparse
|
||||||
|
|
||||||
from primaite.simulator.network.protocols.http import (
|
from primaite.simulator.network.protocols.http import (
|
||||||
HTTPRequestMethod,
|
HttpRequestMethod,
|
||||||
HTTPRequestPacket,
|
HttpRequestPacket,
|
||||||
HTTPResponsePacket,
|
HttpResponsePacket,
|
||||||
HTTPStatusCode,
|
HttpStatusCode,
|
||||||
)
|
)
|
||||||
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
from primaite.simulator.network.transmission.transport_layer import Port
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
@@ -37,52 +37,52 @@ class WebServer(Service):
|
|||||||
# index HTML main file
|
# index HTML main file
|
||||||
self.file_system.create_file(file_name="index.html", folder_name="primaite", real=True)
|
self.file_system.create_file(file_name="index.html", folder_name="primaite", real=True)
|
||||||
|
|
||||||
def _process_http_request(self, payload: HTTPRequestPacket, session_id: Optional[str] = None) -> bool:
|
def _process_http_request(self, payload: HttpRequestPacket, session_id: Optional[str] = None) -> bool:
|
||||||
"""
|
"""
|
||||||
Parse the HTTPRequestPacket.
|
Parse the HttpRequestPacket.
|
||||||
|
|
||||||
:param: payload: Payload containing th HTTPRequestPacket
|
:param: payload: Payload containing th HttpRequestPacket
|
||||||
:type: payload: HTTPRequestPacket
|
:type: payload: HttpRequestPacket
|
||||||
|
|
||||||
:param: session_id: Session id of the http request
|
:param: session_id: Session id of the http request
|
||||||
:type: session_id: Optional[str]
|
:type: session_id: Optional[str]
|
||||||
"""
|
"""
|
||||||
response = HTTPResponsePacket()
|
response = HttpResponsePacket()
|
||||||
|
|
||||||
self.sys_log.info(f"{self.name}: Received HTTP {payload.request_method.name} {payload.request_url}")
|
self.sys_log.info(f"{self.name}: Received HTTP {payload.request_method.name} {payload.request_url}")
|
||||||
|
|
||||||
# check the type of HTTP request
|
# check the type of HTTP request
|
||||||
if payload.request_method == HTTPRequestMethod.GET:
|
if payload.request_method == HttpRequestMethod.GET:
|
||||||
response = self._handle_get_request(payload=payload)
|
response = self._handle_get_request(payload=payload)
|
||||||
|
|
||||||
elif payload.request_method == HTTPRequestMethod.POST:
|
elif payload.request_method == HttpRequestMethod.POST:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# send a method not allowed response
|
# send a method not allowed response
|
||||||
response.status_code = HTTPStatusCode.METHOD_NOT_ALLOWED
|
response.status_code = HttpStatusCode.METHOD_NOT_ALLOWED
|
||||||
|
|
||||||
# send response to web client
|
# send response to web client
|
||||||
self.send(payload=response, session_id=session_id)
|
self.send(payload=response, session_id=session_id)
|
||||||
|
|
||||||
# return true if response is OK
|
# return true if response is OK
|
||||||
return response.status_code == HTTPStatusCode.OK
|
return response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
def _handle_get_request(self, payload: HTTPRequestPacket) -> HTTPResponsePacket:
|
def _handle_get_request(self, payload: HttpRequestPacket) -> HttpResponsePacket:
|
||||||
"""
|
"""
|
||||||
Handle a GET HTTP request.
|
Handle a GET HTTP request.
|
||||||
|
|
||||||
:param: payload: HTTP request payload
|
:param: payload: HTTP request payload
|
||||||
:type: payload: HTTPRequestPacket
|
:type: payload: HttpRequestPacket
|
||||||
"""
|
"""
|
||||||
response = HTTPResponsePacket(status_code=HTTPStatusCode.BAD_REQUEST, payload=payload)
|
response = HttpResponsePacket(status_code=HttpStatusCode.NOT_FOUND, payload=payload)
|
||||||
try:
|
try:
|
||||||
parsed_url = urlparse(payload.request_url)
|
parsed_url = urlparse(payload.request_url)
|
||||||
path = parsed_url.path.strip("/")
|
path = parsed_url.path.strip("/")
|
||||||
|
|
||||||
if len(path) < 1:
|
if len(path) < 1:
|
||||||
# query succeeded
|
# query succeeded
|
||||||
response.status_code = HTTPStatusCode.OK
|
response.status_code = HttpStatusCode.OK
|
||||||
|
|
||||||
if path.startswith("users"):
|
if path.startswith("users"):
|
||||||
# get data from DatabaseServer
|
# get data from DatabaseServer
|
||||||
@@ -90,17 +90,17 @@ class WebServer(Service):
|
|||||||
# get all users
|
# get all users
|
||||||
if db_client.query("SELECT * FROM user;"):
|
if db_client.query("SELECT * FROM user;"):
|
||||||
# query succeeded
|
# query succeeded
|
||||||
response.status_code = HTTPStatusCode.OK
|
response.status_code = HttpStatusCode.OK
|
||||||
|
|
||||||
return response
|
return response
|
||||||
except Exception:
|
except Exception:
|
||||||
# something went wrong on the server
|
# something went wrong on the server
|
||||||
response.status_code = HTTPStatusCode.INTERNAL_SERVER_ERROR
|
response.status_code = HttpStatusCode.INTERNAL_SERVER_ERROR
|
||||||
return response
|
return response
|
||||||
|
|
||||||
def send(
|
def send(
|
||||||
self,
|
self,
|
||||||
payload: HTTPResponsePacket,
|
payload: HttpResponsePacket,
|
||||||
session_id: Optional[str] = None,
|
session_id: Optional[str] = None,
|
||||||
dest_ip_address: Optional[IPv4Address] = None,
|
dest_ip_address: Optional[IPv4Address] = None,
|
||||||
dest_port: Optional[Port] = None,
|
dest_port: Optional[Port] = None,
|
||||||
@@ -138,7 +138,7 @@ class WebServer(Service):
|
|||||||
:param: session_id: The id of the session. Optional.
|
:param: session_id: The id of the session. Optional.
|
||||||
"""
|
"""
|
||||||
# check if the payload is an HTTPPacket
|
# check if the payload is an HTTPPacket
|
||||||
if not isinstance(payload, HTTPRequestPacket):
|
if not isinstance(payload, HttpRequestPacket):
|
||||||
self.sys_log.error("Payload is not an HTTPPacket")
|
self.sys_log.error("Payload is not an HTTPPacket")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
from primaite.simulator.network.hardware.nodes.computer import Computer
|
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||||
from primaite.simulator.network.hardware.nodes.server import Server
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
from primaite.simulator.network.protocols.http import HTTPStatusCode
|
from primaite.simulator.network.protocols.http import HttpStatusCode
|
||||||
from primaite.simulator.system.applications.application import ApplicationOperatingState
|
from primaite.simulator.system.applications.application import ApplicationOperatingState
|
||||||
from primaite.simulator.system.applications.web_browser import WebBrowser
|
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||||
from primaite.simulator.system.services.service import ServiceOperatingState
|
from primaite.simulator.system.services.service import ServiceOperatingState
|
||||||
@@ -17,7 +17,7 @@ def test_web_page_home_page(uc2_network):
|
|||||||
|
|
||||||
# latest reponse should have status code 200
|
# latest reponse should have status code 200
|
||||||
assert web_client.latest_response is not None
|
assert web_client.latest_response is not None
|
||||||
assert web_client.latest_response.status_code == HTTPStatusCode.OK
|
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
|
|
||||||
def test_web_page_get_users_page_request_with_domain_name(uc2_network):
|
def test_web_page_get_users_page_request_with_domain_name(uc2_network):
|
||||||
@@ -31,7 +31,7 @@ def test_web_page_get_users_page_request_with_domain_name(uc2_network):
|
|||||||
|
|
||||||
# latest reponse should have status code 200
|
# latest reponse should have status code 200
|
||||||
assert web_client.latest_response is not None
|
assert web_client.latest_response is not None
|
||||||
assert web_client.latest_response.status_code == HTTPStatusCode.OK
|
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
|
|
||||||
def test_web_page_get_users_page_request_with_ip_address(uc2_network):
|
def test_web_page_get_users_page_request_with_ip_address(uc2_network):
|
||||||
@@ -49,4 +49,4 @@ def test_web_page_get_users_page_request_with_ip_address(uc2_network):
|
|||||||
|
|
||||||
# latest reponse should have status code 200
|
# latest reponse should have status code 200
|
||||||
assert web_client.latest_response is not None
|
assert web_client.latest_response is not None
|
||||||
assert web_client.latest_response.status_code == HTTPStatusCode.OK
|
assert web_client.latest_response.status_code == HttpStatusCode.OK
|
||||||
|
|||||||
@@ -0,0 +1,39 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.nodes.computer import Computer
|
||||||
|
from primaite.simulator.network.protocols.http import HttpResponsePacket, HttpStatusCode
|
||||||
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
|
from primaite.simulator.system.applications.web_browser import WebBrowser
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def web_client() -> Computer:
|
||||||
|
node = Computer(
|
||||||
|
hostname="web_client", ip_address="192.168.1.11", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
|
||||||
|
)
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_web_client(web_client):
|
||||||
|
assert web_client is not None
|
||||||
|
web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"]
|
||||||
|
assert web_browser.name is "WebBrowser"
|
||||||
|
assert web_browser.port is Port.HTTP
|
||||||
|
assert web_browser.protocol is IPProtocol.TCP
|
||||||
|
|
||||||
|
|
||||||
|
def test_receive_invalid_payload(web_client):
|
||||||
|
web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"]
|
||||||
|
|
||||||
|
assert web_browser.receive(payload={}) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_receive_payload(web_client):
|
||||||
|
payload = HttpResponsePacket(status_code=HttpStatusCode.OK)
|
||||||
|
web_browser: WebBrowser = web_client.software_manager.software["WebBrowser"]
|
||||||
|
assert web_browser.latest_response is None
|
||||||
|
|
||||||
|
web_browser.receive(payload=payload)
|
||||||
|
|
||||||
|
assert web_browser.latest_response is not None
|
||||||
@@ -0,0 +1,64 @@
|
|||||||
|
import pytest
|
||||||
|
|
||||||
|
from primaite.simulator.network.hardware.nodes.server import Server
|
||||||
|
from primaite.simulator.network.protocols.http import (
|
||||||
|
HttpRequestMethod,
|
||||||
|
HttpRequestPacket,
|
||||||
|
HttpResponsePacket,
|
||||||
|
HttpStatusCode,
|
||||||
|
)
|
||||||
|
from primaite.simulator.network.transmission.network_layer import IPProtocol
|
||||||
|
from primaite.simulator.network.transmission.transport_layer import Port
|
||||||
|
from primaite.simulator.system.services.web_server.web_server import WebServer
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture(scope="function")
|
||||||
|
def web_server() -> Server:
|
||||||
|
node = Server(
|
||||||
|
hostname="web_server", ip_address="192.168.1.10", subnet_mask="255.255.255.0", default_gateway="192.168.1.1"
|
||||||
|
)
|
||||||
|
node.software_manager.install(software_class=WebServer)
|
||||||
|
node.software_manager.software["WebServer"].start()
|
||||||
|
return node
|
||||||
|
|
||||||
|
|
||||||
|
def test_create_web_server(web_server):
|
||||||
|
assert web_server is not None
|
||||||
|
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
|
||||||
|
assert web_server_service.name is "WebServer"
|
||||||
|
assert web_server_service.port is Port.HTTP
|
||||||
|
assert web_server_service.protocol is IPProtocol.TCP
|
||||||
|
|
||||||
|
|
||||||
|
def test_handling_get_request_not_found_path(web_server):
|
||||||
|
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/fake-path")
|
||||||
|
|
||||||
|
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
|
||||||
|
|
||||||
|
response: HttpResponsePacket = web_server_service._handle_get_request(payload=payload)
|
||||||
|
assert response.status_code == HttpStatusCode.NOT_FOUND
|
||||||
|
|
||||||
|
|
||||||
|
def test_handling_get_request_home_page(web_server):
|
||||||
|
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/")
|
||||||
|
|
||||||
|
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
|
||||||
|
|
||||||
|
response: HttpResponsePacket = web_server_service._handle_get_request(payload=payload)
|
||||||
|
assert response.status_code == HttpStatusCode.OK
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_http_request_get(web_server):
|
||||||
|
payload = HttpRequestPacket(request_method=HttpRequestMethod.GET, request_url="http://domain.com/")
|
||||||
|
|
||||||
|
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
|
||||||
|
|
||||||
|
assert web_server_service._process_http_request(payload=payload) is True
|
||||||
|
|
||||||
|
|
||||||
|
def test_process_http_request_method_not_allowed(web_server):
|
||||||
|
payload = HttpRequestPacket(request_method=HttpRequestMethod.DELETE, request_url="http://domain.com/")
|
||||||
|
|
||||||
|
web_server_service: WebServer = web_server.software_manager.software["WebServer"]
|
||||||
|
|
||||||
|
assert web_server_service._process_http_request(payload=payload) is False
|
||||||
Reference in New Issue
Block a user