class FactSourceApi(BaseObjectApi): def __init__(self, services): super().__init__(description='fact source', obj_class=Source, schema=SourceSchema, ram_key='sources', id_property='id', auth_svc=services['auth_svc']) self._api_manager = BaseApiManager(data_svc=services['data_svc'], file_svc=services['file_svc']) def add_routes(self, app: web.Application): router = app.router router.add_get('/sources', self.get_fact_sources) router.add_get('/sources/{id}', self.get_fact_source_by_id) router.add_post('/sources', self.create_fact_source) router.add_patch('/sources/{id}', self.update_fact_source) router.add_put('/sources/{id}', self.create_or_update_source) @aiohttp_apispec.docs(tags=['sources']) @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema) @aiohttp_apispec.response_schema(SourceSchema(many=True, partial=True)) async def get_fact_sources(self, request: web.Request): sources = await self.get_all_objects(request) return web.json_response(sources) @aiohttp_apispec.docs(tags=['sources']) @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema) @aiohttp_apispec.response_schema(SourceSchema(partial=True)) async def get_fact_source_by_id(self, request: web.Request): source = await self.get_object(request) return web.json_response(source) @aiohttp_apispec.docs(tags=['sources']) @aiohttp_apispec.request_schema(SourceSchema) @aiohttp_apispec.response_schema(SourceSchema) async def create_fact_source(self, request: web.Request): source = await self.create_on_disk_object(request) return web.json_response(source.display) @aiohttp_apispec.docs(tags=['sources']) @aiohttp_apispec.request_schema(SourceSchema(partial=True)) @aiohttp_apispec.response_schema(SourceSchema) async def update_fact_source(self, request: web.Request): source = await self.update_on_disk_object(request) return web.json_response(source.display) @aiohttp_apispec.docs(tags=['sources']) @aiohttp_apispec.request_schema(SourceSchema(partial=True)) @aiohttp_apispec.response_schema(SourceSchema) async def create_or_update_source(self, request: web.Request): source = await self.create_or_update_on_disk_object(request) return web.json_response(source.display)
class OperationSchema(ma.Schema): id = ma.fields.String() name = ma.fields.String(required=True) host_group = ma.fields.List(ma.fields.Nested(AgentSchema()), attribute='agents', dump_only=True) adversary = ma.fields.Nested(AdversarySchema()) jitter = ma.fields.String() planner = ma.fields.Nested(PlannerSchema()) start = ma.fields.DateTime(format=BaseObject.TIME_FORMAT, dump_only=True) state = ma.fields.String() obfuscator = ma.fields.String() autonomous = ma.fields.Integer() chain = ma.fields.Function(lambda obj: [lnk.display for lnk in obj.chain]) auto_close = ma.fields.Boolean() visibility = ma.fields.Integer() objective = ma.fields.Nested(ObjectiveSchema()) use_learning_parsers = ma.fields.Boolean() group = ma.fields.String(missing='') source = ma.fields.Nested(SourceSchema()) @ma.pre_load() def remove_properties(self, data, **_): data.pop('host_group', None) data.pop('start', None) data.pop('chain', None) data.pop('objective', None) return data @ma.post_load def build_operation(self, data, **kwargs): return None if kwargs.get('partial') is True else Operation(**data)
async def _construct_and_dump_source(self, source_id: str): source = await self.services['data_svc'].locate( 'sources', match=dict(id=source_id)) if not source: source = (await self.services['data_svc'].locate( 'sources', match=dict(name='basic'))) return SourceSchema().dump(source[0])
def expected_replaced_source_dump(replaced_source_payload, mocker, mock_time): with mocker.patch( 'app.objects.secondclass.c_fact.datetime') as mock_datetime: mock_datetime.return_value = mock_datetime mock_datetime.now.return_value = mock_time source = SourceSchema().load(replaced_source_payload) dumped_obj = source.display_schema.dump(source) dumped_obj['relationships'][0]['unique'] = mock.ANY return dumped_obj
def test_operation(test_adversary, test_planner, test_source): expected_operation = { 'name': '123', 'adversary': AdversarySchema().dump(test_adversary), 'state': 'paused', 'id': '123', 'group': 'red', 'autonomous': 0, 'planner': PlannerSchema().dump(test_planner), 'source': SourceSchema().dump(test_source), 'jitter': '2/8', 'visibility': 50, 'auto_close': False, 'obfuscator': 'plain-text', 'use_learning_parsers': False } return expected_operation
async def test_create_operation_existing_relationships( self, api_v2_client, api_cookies, test_source_existing_relationships): payload = dict( name='op_existing_relationships', id='456', planner={'id': '123'}, adversary={'adversary_id': '123'}, source=SourceSchema().dump(test_source_existing_relationships)) resp = await api_v2_client.post('/api/v2/operations', cookies=api_cookies, json=payload) assert resp.status == HTTPStatus.OK op_data = await resp.json() assert op_data['name'] == payload['name'] assert op_data['start'] assert op_data['planner']['id'] == payload['planner']['id'] assert op_data['source']['id'] == payload['source']['id'] assert len(op_data['source']['relationships']) == len( payload['source']['relationships'])
class FactSourceApi(BaseObjectApi): def __init__(self, services): super().__init__(description='Fact Source', obj_class=Source, schema=SourceSchema, ram_key='sources', id_property='id', auth_svc=services['auth_svc']) self._api_manager = BaseApiManager(data_svc=services['data_svc'], file_svc=services['file_svc']) def add_routes(self, app: web.Application): router = app.router router.add_get('/sources', self.get_fact_sources) router.add_get('/sources/{id}', self.get_fact_source_by_id) router.add_post('/sources', self.create_fact_source) router.add_patch('/sources/{id}', self.update_fact_source) router.add_put('/sources/{id}', self.create_or_update_source) router.add_delete('/sources/{id}', self.delete_source) @aiohttp_apispec.docs( tags=['sources'], summary='Retrieve all Fact Sources.', description= 'Returns a list of all Fact Sources, including custom-created ones.') @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema) @aiohttp_apispec.response_schema( SourceSchema(many=True, partial=True), description= 'Returns a list of all Sources dumped in SourceSchema format.') async def get_fact_sources(self, request: web.Request): sources = await self.get_all_objects(request) return web.json_response(sources) @aiohttp_apispec.docs( tags=['sources'], summary='Retrieve a Fact Source by its id.', description='Returns a Fact Source, given a source id.', parameters=[{ 'in': 'path', 'name': 'id', 'description': 'The id of the Fact Source', 'schema': { 'type': 'string' }, 'required': 'true' }]) @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema) @aiohttp_apispec.response_schema( SourceSchema(partial=True), description='Returns a single Source dumped in SourceSchema format.') async def get_fact_source_by_id(self, request: web.Request): source = await self.get_object(request) return web.json_response(source) @aiohttp_apispec.docs( tags=['sources'], summary='Create a Fact Source.', description= 'Create a new Fact Source using the format provided in the SourceSchema.' ) @aiohttp_apispec.docs(tags=['sources']) @aiohttp_apispec.request_schema(SourceSchema) @aiohttp_apispec.response_schema( SourceSchema, description='Returns a single Source dumped in SourceSchema format.') async def create_fact_source(self, request: web.Request): source = await self.create_on_disk_object(request) return web.json_response(source.display) @aiohttp_apispec.docs( tags=['sources'], summary='Update an existing Fact Source.', description= 'Returns an updated Fact Source. All fields in a Fact Source can be updated, except for "id" and "adjustments".', parameters=[{ 'in': 'path', 'name': 'id', 'description': 'The id of the Fact Source.', 'schema': { 'type': 'string' }, 'required': 'true' }]) @aiohttp_apispec.request_schema(SourceSchema(partial=True)) @aiohttp_apispec.response_schema( SourceSchema, description='Returns a single Source dumped in SourceSchema format.') async def update_fact_source(self, request: web.Request): source = await self.update_on_disk_object(request) return web.json_response(source.display) @aiohttp_apispec.docs( tags=['sources'], summary='Update an existing or create a new Fact Source.', description= 'Use fields from the SourceSchema in the request body to replace an existing Fact Source or create a new Fact Source.', parameters=[{ 'in': 'path', 'name': 'id', 'description': 'The id of the Fact Source.', 'schema': { 'type': 'string' }, 'required': 'true' }]) @aiohttp_apispec.request_schema(SourceSchema(partial=True)) @aiohttp_apispec.response_schema( SourceSchema, description='Returns a single Source dumped in SourceSchema format.') async def create_or_update_source(self, request: web.Request): source = await self.create_or_update_on_disk_object(request) return web.json_response(source.display) @aiohttp_apispec.docs(tags=['sources'], summary='Delete an existing Fact Source.', description='Delete a Fact Source, given its id.', parameters=[{ 'in': 'path', 'name': 'id', 'description': 'The id of the Fact Source to be deleted.', 'schema': { 'type': 'string' }, 'required': 'true' }]) @aiohttp_apispec.response_schema(SourceSchema, description='Returns DELETE status.') async def delete_source(self, request: web.Request): await self.delete_object(request) return web.HTTPNoContent()