Merge branch '2106-refactor-island-mode' into develop

Resolves #2143
This commit is contained in:
Mike Salvatore 2022-08-01 11:07:42 -04:00
commit dd882df1cc
9 changed files with 38 additions and 33 deletions

View File

@ -46,6 +46,7 @@ Changelog](https://keepachangelog.com/en/1.0.0/).
#1761, #1695, #1605, #2028, #2003 #1761, #1695, #1605, #2028, #2003
- `/api/island-mode` to accept and return new "unset" mode. #2036 - `/api/island-mode` to accept and return new "unset" mode. #2036
- `/api/version-update` to `api/island/version`. #2109 - `/api/version-update` to `api/island/version`. #2109
- The `/api/island-mode` to `/api/island/mode`. #2106
### Removed ### Removed
- VSFTPD exploiter. #1533 - VSFTPD exploiter. #1533

View File

@ -120,7 +120,7 @@ class MonkeyIslandClient(object):
assert False assert False
def _reset_island_mode(self): def _reset_island_mode(self):
if self.requests.post("api/island-mode", data='{"mode": "unset"}').ok: if self.requests.put("api/island/mode", data='{"mode": "unset"}').ok:
LOGGER.info("Resseting island mode after the test.") LOGGER.info("Resseting island mode after the test.")
else: else:
LOGGER.error("Failed to reset island mode") LOGGER.error("Failed to reset island mode")

View File

@ -82,6 +82,12 @@ class MonkeyIslandRequests(object):
self.addr + url, data=data, headers=self.get_jwt_header(), verify=False self.addr + url, data=data, headers=self.get_jwt_header(), verify=False
) )
@_Decorators.refresh_jwt_token
def put(self, url, data):
return requests.put( # noqa: DUO123
self.addr + url, data=data, headers=self.get_jwt_header(), verify=False
)
@_Decorators.refresh_jwt_token @_Decorators.refresh_jwt_token
def post_json(self, url, json: Dict): def post_json(self, url, json: Dict):
return requests.post( # noqa: DUO123 return requests.post( # noqa: DUO123

View File

@ -1,5 +1,6 @@
import json import json
import logging import logging
from http import HTTPStatus
from flask import make_response, request from flask import make_response, request
@ -12,30 +13,26 @@ logger = logging.getLogger(__name__)
class IslandMode(AbstractResource): class IslandMode(AbstractResource):
urls = ["/api/island-mode"] urls = ["/api/island/mode"]
def __init__(self, island_mode_service: IslandModeService): def __init__(self, island_mode_service: IslandModeService):
self._island_mode_service = island_mode_service self._island_mode_service = island_mode_service
# API Spec: Instead of POST, this should be PUT
@jwt_required @jwt_required
def post(self): def put(self):
try: try:
body = json.loads(request.data) body = json.loads(request.data)
mode = IslandModeEnum(body.get("mode")) mode = IslandModeEnum(body.get("mode"))
self._island_mode_service.set_mode(mode) self._island_mode_service.set_mode(mode)
# TODO: Do any of these returns need a body and make_response? What happens if we just return {}, HTTPStatus.NO_CONTENT
# return the response code?
# API Spec: This should be 204 (NO CONTENT)
return make_response({}, 200)
except (AttributeError, json.decoder.JSONDecodeError): except (AttributeError, json.decoder.JSONDecodeError):
return make_response({}, 400) return {}, HTTPStatus.BAD_REQUEST
except ValueError: except ValueError:
return make_response({}, 422) return {}, HTTPStatus.UNPROCESSABLE_ENTITY
@jwt_required @jwt_required
def get(self): def get(self):
island_mode = self._island_mode_service.get_mode() island_mode = self._island_mode_service.get_mode()
return make_response({"mode": island_mode.value}, 200) return make_response({"mode": island_mode.value}, HTTPStatus.OK)

View File

@ -12,11 +12,11 @@ export class Response{
} }
class IslandHttpClient extends AuthComponent { class IslandHttpClient extends AuthComponent {
post(endpoint: string, contents: any): Promise<Response>{ put(endpoint: string, contents: any): Promise<Response>{
let status = null; let status = null;
return this.authFetch(endpoint, return this.authFetch(endpoint,
{ {
method: 'POST', method: 'PUT',
headers: {'Content-Type': 'application/json'}, headers: {'Content-Type': 'application/json'},
body: JSON.stringify(contents) body: JSON.stringify(contents)
}) })

View File

@ -113,7 +113,7 @@ class AppComponent extends AuthComponent {
}; };
setMode = () => { setMode = () => {
return IslandHttpClient.get('/api/island-mode') return IslandHttpClient.get('/api/island/mode')
.then(res => { .then(res => {
this.setState({islandMode: res.body.mode}); this.setState({islandMode: res.body.mode});
}); });

View File

@ -74,7 +74,7 @@ const LandingPageComponent = (props: Props) => {
} }
function setScenario(scenario: string) { function setScenario(scenario: string) {
IslandHttpClient.post('/api/island-mode', {'mode': scenario}) IslandHttpClient.put('/api/island/mode', {'mode': scenario})
.then(() => { .then(() => {
props.onStatusChange(); props.onStatusChange();
}); });

View File

@ -96,7 +96,7 @@ const IslandResetModal = (props: Props) => {
function clearSimulationData(callback: () => void) { function clearSimulationData(callback: () => void) {
auth.authFetch('/api/clear-simulation-data', {method: 'POST'}) auth.authFetch('/api/clear-simulation-data', {method: 'POST'})
.then(res => { .then(res => {
if (res.status === 200) { if (res.ok) {
callback(); callback();
} }
}) })
@ -104,19 +104,19 @@ const IslandResetModal = (props: Props) => {
function resetAll() { function resetAll() {
auth.authFetch('/api/reset-agent-configuration', {method: 'POST'}) auth.authFetch('/api/reset-agent-configuration', {method: 'POST'})
.then(res => { .then(res => {
if (res.status === 200) { if (res.ok) {
return auth.authFetch('/api/clear-simulation-data', {method: 'POST'}) return auth.authFetch('/api/clear-simulation-data', {method: 'POST'})
}}) }})
.then(res => { .then(res => {
if (res.status === 200) { if (res.ok) {
return auth.authFetch('/api/propagation-credentials/configured-credentials', {method: 'PUT', body:'[]'}) return auth.authFetch('/api/propagation-credentials/configured-credentials', {method: 'PUT', body:'[]'})
}}) }})
.then(res => { .then(res => {
if (res.status === 200) { if (res.ok) {
return auth.authFetch('/api/island-mode', {method: 'POST', body: '{"mode": "unset"}'}) return auth.authFetch('/api/island/mode', {method: 'PUT', body: '{"mode": "unset"}'})
}}) }})
.then(res => { .then(res => {
if (res.status !== 200) { if (!res.ok) {
throw 'Error resetting the simulation' throw 'Error resetting the simulation'
} }
}) })

View File

@ -1,4 +1,5 @@
import json import json
from http import HTTPStatus
from unittest.mock import MagicMock from unittest.mock import MagicMock
import pytest import pytest
@ -36,23 +37,23 @@ def flask_client(build_flask_client):
[IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value, IslandMode.UNSET.value], [IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value, IslandMode.UNSET.value],
) )
def test_island_mode_post(flask_client, mode): def test_island_mode_post(flask_client, mode):
resp = flask_client.post( resp = flask_client.put(
IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True
) )
assert resp.status_code == 200 assert resp.status_code == HTTPStatus.NO_CONTENT
def test_island_mode_post__invalid_mode(flask_client): def test_island_mode_post__invalid_mode(flask_client):
resp = flask_client.post( resp = flask_client.put(
IslandModeResource.urls[0], data=json.dumps({"mode": "bogus mode"}), follow_redirects=True IslandModeResource.urls[0], data=json.dumps({"mode": "bogus mode"}), follow_redirects=True
) )
assert resp.status_code == 422 assert resp.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
@pytest.mark.parametrize("invalid_json", ["42", "{test"]) @pytest.mark.parametrize("invalid_json", ["42", "{test"])
def test_island_mode_post__invalid_json(flask_client, invalid_json): def test_island_mode_post__invalid_json(flask_client, invalid_json):
resp = flask_client.post(IslandModeResource.urls[0], data="{test", follow_redirects=True) resp = flask_client.put(IslandModeResource.urls[0], data="{test", follow_redirects=True)
assert resp.status_code == 400 assert resp.status_code == HTTPStatus.BAD_REQUEST
def test_island_mode_post__internal_server_error(build_flask_client): def test_island_mode_post__internal_server_error(build_flask_client):
@ -63,29 +64,29 @@ def test_island_mode_post__internal_server_error(build_flask_client):
container.register_instance(IslandModeService, mock_island_mode_service) container.register_instance(IslandModeService, mock_island_mode_service)
with build_flask_client(container) as flask_client: with build_flask_client(container) as flask_client:
resp = flask_client.post( resp = flask_client.put(
IslandModeResource.urls[0], IslandModeResource.urls[0],
data=json.dumps({"mode": IslandMode.RANSOMWARE.value}), data=json.dumps({"mode": IslandMode.RANSOMWARE.value}),
follow_redirects=True, follow_redirects=True,
) )
assert resp.status_code == 500 assert resp.status_code == HTTPStatus.INTERNAL_SERVER_ERROR
@pytest.mark.parametrize("mode", [IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value]) @pytest.mark.parametrize("mode", [IslandMode.RANSOMWARE.value, IslandMode.ADVANCED.value])
def test_island_mode_endpoint(flask_client, mode): def test_island_mode_endpoint(flask_client, mode):
flask_client.post( flask_client.put(
IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True IslandModeResource.urls[0], data=json.dumps({"mode": mode}), follow_redirects=True
) )
resp = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) resp = flask_client.get(IslandModeResource.urls[0], follow_redirects=True)
assert resp.status_code == 200 assert resp.status_code == HTTPStatus.OK
assert json.loads(resp.data)["mode"] == mode assert json.loads(resp.data)["mode"] == mode
def test_island_mode_endpoint__invalid_mode(flask_client): def test_island_mode_endpoint__invalid_mode(flask_client):
resp_post = flask_client.post( resp_post = flask_client.put(
IslandModeResource.urls[0], data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True IslandModeResource.urls[0], data=json.dumps({"mode": "bogus_mode"}), follow_redirects=True
) )
resp_get = flask_client.get(IslandModeResource.urls[0], follow_redirects=True) resp_get = flask_client.get(IslandModeResource.urls[0], follow_redirects=True)
assert resp_post.status_code == 422 assert resp_post.status_code == HTTPStatus.UNPROCESSABLE_ENTITY
assert json.loads(resp_get.data)["mode"] == IslandMode.UNSET.value assert json.loads(resp_get.data)["mode"] == IslandMode.UNSET.value