Ejemplo n.º 1
0
class AgentFieldsSchema(ma.Schema):

    paw = ma.fields.String()
    group = ma.fields.String()
    architecture = ma.fields.String()
    platform = ma.fields.String()
    server = ma.fields.String()
    username = ma.fields.String()
    location = ma.fields.String()
    pid = ma.fields.Integer()
    ppid = ma.fields.Integer()
    trusted = ma.fields.Boolean()
    last_seen = ma.fields.DateTime(format='%Y-%m-%d %H:%M:%S')
    sleep_min = ma.fields.Integer()
    sleep_max = ma.fields.Integer()
    executors = ma.fields.List(ma.fields.String())
    privilege = ma.fields.String()
    display_name = ma.fields.String()
    exe_name = ma.fields.String()
    host = ma.fields.String()
    watchdog = ma.fields.Integer()
    contact = ma.fields.String()
    pending_contact = ma.fields.String()
    links = ma.fields.List(ma.fields.Nested(LinkSchema()))
    proxy_receivers = ma.fields.Dict(keys=ma.fields.String(), values=ma.fields.List(ma.fields.String()))
    proxy_chain = ma.fields.List(ma.fields.List(ma.fields.String()))
    origin_link_id = ma.fields.Integer()
    deadman_enabled = ma.fields.Boolean()
    available_contacts = ma.fields.List(ma.fields.String())
    created = ma.fields.DateTime(format='%Y-%m-%d %H:%M:%S')

    @ma.pre_load
    def remove_nulls(self, in_data, **_):
        return {k: v for k, v in in_data.items() if v is not None}
Ejemplo n.º 2
0
class ProcessNodeSchema(ma.Schema):
    pid = ma.fields.Integer()
    link = ma.fields.Nested(LinkSchema())
    parent_guid = ma.fields.String()
    child_guids = ma.fields.List(ma.fields.String())

    @ma.post_load()
    def build_pidnode(self, data, **_):
        return ProcessNode(**data)
Ejemplo n.º 3
0
class OperationApi(BaseObjectApi):
    def __init__(self, services):
        super().__init__(description='operation', obj_class=Operation, schema=OperationSchema, ram_key='operations',
                         id_property='id', auth_svc=services['auth_svc'])
        self._api_manager = OperationApiManager(services)

    def add_routes(self, app: web.Application):
        router = app.router
        router.add_get('/operations', self.get_operations)
        router.add_get('/operations/{id}', self.get_operation_by_id)
        router.add_post('/operations', self.create_operation)
        router.add_patch('/operations/{id}', self.update_operation)
        router.add_delete('/operations/{id}', self.delete_operation)
        router.add_post('/operations/{id}/report', self.get_operation_report)
        router.add_post('/operations/{id}/event-logs', self.get_operation_event_logs)
        router.add_get('/operations/{id}/links', self.get_operation_links)
        router.add_get('/operations/{id}/links/{link_id}', self.get_operation_link)
        router.add_get('/operations/{id}/links/{link_id}/result', self.get_operation_link_result)
        router.add_patch('/operations/{id}/links/{link_id}', self.update_operation_link)
        router.add_post('/operations/{id}/potential-links', self.create_potential_link)
        router.add_get('/operations/{id}/potential-links', self.get_potential_links)
        router.add_get('/operations/{id}/potential-links/{paw}', self.get_potential_links_by_paw)

    @aiohttp_apispec.docs(tags=['operations'],
                          summary='Retrieve operations',
                          description='Retrieve all CALDERA operations from memory.  Use fields from the '
                                      '`BaseGetAllQuerySchema` in the request body to filter.')
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(OperationSchema(many=True, partial=True),
                                     description='The response is a list of all operations.')
    async def get_operations(self, request: web.Request):
        operations = await self.get_all_objects(request)
        return web.json_response(operations)

    @aiohttp_apispec.docs(tags=['operations'],
                          summary='Retrieve an operation by operation id',
                          description='Retrieve one CALDERA operation from memory based on the operation id (String '
                                      'UUID).  Use fields from the `BaseGetOneQuerySchema` in the request body to add '
                                      '`include` and `exclude` filters.',
                          parameters=[{
                              'in': 'path',
                              'name': 'id',
                              'schema': {'type': 'string'},
                              'required': 'true',
                              'description': 'UUID of the Operation object to be retrieved.'
                          }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(OperationSchema(partial=True),
                                     description='The response is the operation with the specified id, if any.')
    async def get_operation_by_id(self, request: web.Request):
        operation = await self.get_object(request)
        return web.json_response(operation)

    @aiohttp_apispec.docs(tags=['operations'],
                          summary='Create a new CALDERA operation record',
                          description='Create a new CALDERA operation using the format provided in the '
                                      '`OperationSchema`. Required nested schema fields are as follows: '
                                      '"adversary.adversary_id", "planner.planner_id", and "source.id"')
    @aiohttp_apispec.request_schema(OperationSchema)
    @aiohttp_apispec.response_schema(OperationSchema,
                                     description='The response is the newly-created operation report.')
    async def create_operation(self, request: web.Request):
        operation = await self.create_object(request)
        return web.json_response(operation.display)

    @aiohttp_apispec.docs(tags=['operations'],
                          summary='Update fields within an operation',
                          description='Update one CALDERA operation in memory based on the operation id (String '
                                      'UUID). The `state`, `autonomous` and `obfuscator` fields in the operation '
                                      'object may be edited in the request body using the `OperationSchema`.',
                          parameters=[{
                              'in': 'path',
                              'name': 'id',
                              'schema': {'type': 'string'},
                              'required': 'true',
                              'description': 'UUID of the Operation object to be retrieved.'
                          }])
    @aiohttp_apispec.request_schema(OperationSchema(partial=True, only=['state', 'autonomous', 'obfuscator']))
    @aiohttp_apispec.response_schema(OperationSchema(partial=True),
                                     description='The response is the updated operation, including user modifications.')
    async def update_operation(self, request: web.Request):
        operation = await self.update_object(request)
        return web.json_response(operation.display)

    @aiohttp_apispec.docs(tags=['operations'],
                          summary='Delete an operation by operation id',
                          description='Delete one CALDERA operation from memory based on the operation id (String '
                                      'UUID).',
                          parameters=[{
                              'in': 'path',
                              'name': 'id',
                              'schema': {'type': 'string'},
                              'required': 'true',
                              'description': 'UUID of the Operation object to be retrieved.'
                          }])
    @aiohttp_apispec.response_schema(OperationSchema,
                                     description='There is an empty response from a successful delete request.')
    async def delete_operation(self, request: web.Request):
        await self.delete_object(request)
        return web.HTTPNoContent()

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.request_schema(OperationOutputRequestSchema)
    async def get_operation_report(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        output = await self._read_output_parameter_(request)
        report = await self._api_manager.get_operation_report(operation_id, access, output)
        return web.json_response(report)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.request_schema(OperationOutputRequestSchema)
    async def get_operation_event_logs(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        output = await self._read_output_parameter_(request)
        report = await self._api_manager.get_operation_event_logs(operation_id, access, output)
        return web.json_response(report)

    @aiohttp_apispec.docs(tags=['operations'], summary='The only required fields for this endpoint are "paw", '
                                                       '"executor.name", "executor.command", and "executor.platform". '
                                                       '"executor.command" is expected to be unencoded.')
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(many=True, partial=True))
    async def get_operation_links(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        links = await self._api_manager.get_operation_links(operation_id, access)
        return web.json_response(links)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(partial=True))
    async def get_operation_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        link = await self._api_manager.get_operation_link(operation_id, link_id, access)
        return web.json_response(link)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(partial=True))
    async def get_operation_link_result(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        result = await self._api_manager.get_operation_link_result(operation_id, link_id, access)
        return web.json_response(result)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.request_schema(LinkSchema(partial=True, only=['command', 'status']))
    @aiohttp_apispec.response_schema(LinkSchema)
    async def update_operation_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        data = await request.json()
        link = await self._api_manager.update_operation_link(operation_id, link_id, data, access)
        return web.json_response(link)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.request_schema(LinkSchema)
    @aiohttp_apispec.response_schema(LinkSchema)
    async def create_potential_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        data = await request.json()
        potential_link = await self._api_manager.create_potential_link(operation_id, data, access)
        return web.json_response(potential_link)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(many=True, partial=True))
    async def get_potential_links(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        potential_links = await self._api_manager.get_potential_links(operation_id, access)
        return web.json_response(potential_links)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(partial=True))
    async def get_potential_links_by_paw(self, request: web.Request):
        operation_id = request.match_info.get('id')
        paw = request.match_info.get('paw')
        access = await self.get_request_permissions(request)
        potential_links = await self._api_manager.get_potential_links(operation_id, access, paw)
        return web.json_response(potential_links)

    async def create_object(self, request: web.Request):
        data = await request.json()
        await self._error_if_object_with_id_exists(data.get(self.id_property))
        access = await self.get_request_permissions(request)
        return await self._api_manager.create_object_from_schema(self.schema, data, access)

    async def update_object(self, request: web.Request):
        data, access, obj_id, query, search = await self._parse_common_data_from_request(request)
        obj = await self._api_manager.find_and_update_object(self.ram_key, data, search)
        if not obj:
            raise JsonHttpNotFound(f'{self.description.capitalize()} not found: {obj_id}')
        return obj

    async def _read_output_parameter_(self, request: web.Request):
        raw_body = await request.read()
        output = False
        if raw_body:
            output = json.loads(raw_body).get('enable_agent_output', False)
        return output
Ejemplo n.º 4
0
class OperationApi(BaseObjectApi):
    def __init__(self, services):
        super().__init__(description='operation',
                         obj_class=Operation,
                         schema=OperationSchema,
                         ram_key='operations',
                         id_property='id',
                         auth_svc=services['auth_svc'])
        self._api_manager = OperationApiManager(services)

    def add_routes(self, app: web.Application):
        router = app.router
        router.add_get('/operations', self.get_operations)
        router.add_get('/operations/{id}', self.get_operation_by_id)
        router.add_post('/operations', self.create_operation)
        router.add_patch('/operations/{id}', self.update_operation)
        router.add_delete('/operations/{id}', self.delete_operation)
        router.add_get('/operations/{id}/report', self.get_operation_report)
        router.add_get('/operations/{id}/links', self.get_operation_links)
        router.add_get('/operations/{id}/links/{link_id}',
                       self.get_operation_link)
        router.add_patch('/operations/{id}/links/{link_id}',
                         self.update_operation_link)
        router.add_post('/operations/{id}/potential-links',
                        self.create_potential_link)
        router.add_get('/operations/{id}/potential-links',
                       self.get_potential_links)
        router.add_get('/operations/{id}/potential-links/{paw}',
                       self.get_potential_links_by_paw)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(OperationSchema(many=True, partial=True))
    async def get_operations(self, request: web.Request):
        operations = await self.get_all_objects(request)
        return web.json_response(operations)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(OperationSchema(partial=True))
    async def get_operation_by_id(self, request: web.Request):
        operation = await self.get_object(request)
        return web.json_response(operation)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.request_schema(OperationSchema)
    @aiohttp_apispec.response_schema(OperationSchema)
    async def create_operation(self, request: web.Request):
        operation = await self.create_object(request)
        return web.json_response(operation.display)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.request_schema(OperationSchema(partial=True))
    @aiohttp_apispec.response_schema(OperationSchema(partial=True))
    async def update_operation(self, request: web.Request):
        operation = await self.update_object(request)
        return web.json_response(operation.display)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.response_schema(OperationSchema)
    async def delete_operation(self, request: web.Request):
        await self.delete_object(request)
        return web.HTTPNoContent()

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    async def get_operation_report(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        report = await self._api_manager.get_operation_report(
            operation_id, access)
        return web.json_response(report)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(many=True, partial=True))
    async def get_operation_links(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        links = await self._api_manager.get_operation_links(
            operation_id, access)
        return web.json_response(links)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(partial=True))
    async def get_operation_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        link = await self._api_manager.get_operation_link(
            operation_id, link_id, access)
        return web.json_response(link)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.request_schema(LinkSchema(partial=True))
    @aiohttp_apispec.response_schema(LinkSchema)
    async def update_operation_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        data = await request.json()
        link = await self._api_manager.update_operation_link(
            operation_id, link_id, data, access)
        return web.json_response(link)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.request_schema(LinkSchema)
    @aiohttp_apispec.response_schema(LinkSchema)
    async def create_potential_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        data = await request.json()
        potential_link = await self._api_manager.create_potential_link(
            operation_id, data, access)
        return web.json_response(potential_link)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(many=True, partial=True))
    async def get_potential_links(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        potential_links = await self._api_manager.get_potential_links(
            operation_id, access)
        return web.json_response(potential_links)

    @aiohttp_apispec.docs(tags=['operations'])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(LinkSchema(partial=True))
    async def get_potential_links_by_paw(self, request: web.Request):
        operation_id = request.match_info.get('id')
        paw = request.match_info.get('paw')
        access = await self.get_request_permissions(request)
        potential_links = await self._api_manager.get_potential_links(
            operation_id, access, paw)
        return web.json_response(potential_links)

    '''Overridden Methods'''

    async def create_object(self, request: web.Request):
        data = await request.json()
        await self._error_if_object_with_id_exists(data.get(self.id_property))
        access = await self.get_request_permissions(request)
        return await self._api_manager.create_object_from_schema(
            self.schema, data, access)

    async def update_object(self, request: web.Request):
        data, access, obj_id, query, search = await self._parse_common_data_from_request(
            request)
        obj = await self._api_manager.find_and_update_object(
            self.ram_key, data, search)
        if not obj:
            raise JsonHttpNotFound(
                f'{self.description.capitalize()} not found: {obj_id}')
        return obj
Ejemplo n.º 5
0
class OperationApi(BaseObjectApi):
    def __init__(self, services):
        super().__init__(description='operation',
                         obj_class=Operation,
                         schema=OperationSchema,
                         ram_key='operations',
                         id_property='id',
                         auth_svc=services['auth_svc'])
        self._api_manager = OperationApiManager(services)

    def add_routes(self, app: web.Application):
        router = app.router
        router.add_get('/operations', self.get_operations)
        router.add_get('/operations/{id}', self.get_operation_by_id)
        router.add_post('/operations', self.create_operation)
        router.add_patch('/operations/{id}', self.update_operation)
        router.add_delete('/operations/{id}', self.delete_operation)
        router.add_post('/operations/{id}/report', self.get_operation_report)
        router.add_post('/operations/{id}/event-logs',
                        self.get_operation_event_logs)
        router.add_get('/operations/{id}/links', self.get_operation_links)
        router.add_get('/operations/{id}/links/{link_id}',
                       self.get_operation_link)
        router.add_get('/operations/{id}/links/{link_id}/result',
                       self.get_operation_link_result)
        router.add_patch('/operations/{id}/links/{link_id}',
                         self.update_operation_link)
        router.add_post('/operations/{id}/potential-links',
                        self.create_potential_link)
        router.add_get('/operations/{id}/potential-links',
                       self.get_potential_links)
        router.add_get('/operations/{id}/potential-links/{paw}',
                       self.get_potential_links_by_paw)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Retrieve operations',
        description=
        'Retrieve all CALDERA operations from memory.  Use fields from the '
        '`BaseGetAllQuerySchema` in the request body to filter.')
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(
        OperationSchema(many=True, partial=True),
        description='The response is a list of all operations.')
    async def get_operations(self, request: web.Request):
        operations = await self.get_all_objects(request)
        return web.json_response(operations)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Retrieve an operation by operation id',
        description=
        'Retrieve one CALDERA operation from memory based on the operation id (String '
        'UUID).  Use fields from the `BaseGetOneQuerySchema` in the request body to add '
        '`include` and `exclude` filters.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the Operation object to be retrieved.'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(
        OperationSchema(partial=True),
        description=
        'The response is the operation with the specified id, if any.')
    async def get_operation_by_id(self, request: web.Request):
        operation = await self.get_object(request)
        return web.json_response(operation)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Create a new CALDERA operation record',
        description=
        'Create a new CALDERA operation using the format provided in the '
        '`OperationSchema`. Required schema fields are as follows: "name", '
        '"adversary.adversary_id", "planner.planner_id", and "source.id"')
    @aiohttp_apispec.request_schema(OperationSchema)
    @aiohttp_apispec.response_schema(
        OperationSchema,
        description='The response is the newly-created operation report.')
    async def create_operation(self, request: web.Request):
        operation = await self.create_object(request)
        return web.json_response(operation.display)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Update fields within an operation',
        description=
        'Update one CALDERA operation in memory based on the operation id (String '
        'UUID). The `state`, `autonomous` and `obfuscator` fields in the operation '
        'object may be edited in the request body using the `OperationSchema`.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the Operation object to be retrieved.'
        }])
    @aiohttp_apispec.request_schema(
        OperationSchema(partial=True,
                        only=['state', 'autonomous', 'obfuscator']))
    @aiohttp_apispec.response_schema(
        OperationSchema(partial=True),
        description=
        'The response is the updated operation, including user modifications.')
    async def update_operation(self, request: web.Request):
        operation = await self.update_object(request)
        return web.json_response(operation.display)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Delete an operation by operation id',
        description=
        'Delete one CALDERA operation from memory based on the operation id (String '
        'UUID).',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the Operation object to be retrieved.'
        }])
    @aiohttp_apispec.response_schema(
        OperationSchema,
        description=
        'There is an empty response from a successful delete request.')
    async def delete_operation(self, request: web.Request):
        await self.delete_object(request)
        return web.HTTPNoContent()

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Get Operation Report',
        description='Retrieves the report for a given operation_id.',
        parameters=[{
            'in': 'path',
            'name': 'id',
            'operation_id': 'Unique ID for operation',
            'access': 'Format for report',
            'output': 'Boolean for Agent Output in report',
            'schema': {
                'type': 'string'
            },
            'required': 'true'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.request_schema(OperationOutputRequestSchema)
    @aiohttp_apispec.response_schema(OperationOutputRequestSchema)
    async def get_operation_report(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        output = await self._read_output_parameter_(request)
        report = await self._api_manager.get_operation_report(
            operation_id, access, output)
        return web.json_response(report)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Get Operation Event Logs',
        description='Retrieves the event logs for a given operation_id.',
        parameters=[{
            'in': 'path',
            'name': 'id',
            'operation_id': 'Unique ID for operation',
            'access': 'Format for report',
            'output': 'Boolean for Agent Output in report',
            'schema': {
                'type': 'string'
            },
            'required': 'true'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.request_schema(OperationOutputRequestSchema)
    @aiohttp_apispec.response_schema(OperationOutputRequestSchema)
    async def get_operation_event_logs(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        output = await self._read_output_parameter_(request)
        report = await self._api_manager.get_operation_event_logs(
            operation_id, access, output)
        return web.json_response(report)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Get Links from Operation',
        description='Retrieves all links for a given operation_id.',
        parameters=[{
            'in': 'path',
            'name': 'id',
            'operation_id': 'Unique ID for operation',
            'schema': {
                'type': 'string'
            },
            'required': 'true'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(
        LinkSchema(many=True, partial=True),
        description=
        'All links contained in operation with the given `id` (String UUID).')
    async def get_operation_links(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        links = await self._api_manager.get_operation_links(
            operation_id, access)
        return web.json_response(links)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Retrieve a specified link from an operation',
        description=
        'Retrieve the link with the provided `link_id` (String UUID) from the operation '
        'with the given operation `id` (String UUID). Use fields from the '
        '`BaseGetOneQuerySchema` in the request body to add `include` and `exclude` '
        'filters.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'String UUID of the Operation containing desired link.'
        }, {
            'in':
            'path',
            'name':
            'link_id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'String UUID of the Link with the above operation.'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(
        LinkSchema(partial=True),
        description=
        'The link matching the provided `link_id` within the operation '
        'matching `id`. Use fields from the `BaseGetOneQuerySchema` in the '
        'request body to add `include` and `exclude` filters.')
    async def get_operation_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        link = await self._api_manager.get_operation_link(
            operation_id, link_id, access)
        return web.json_response(link)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Retrieve the result of a link',
        description=
        'Retrieve the results of one link from memory based on the operation id (String '
        'UUID) and link id (String UUID).  Use fields from the `BaseGetOneQuerySchema` in the '
        'request body to add `include` and `exclude` filters.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the operation object to be retrieved.'
        }, {
            'in':
            'path',
            'name':
            'link_id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the link object to retrieve results of.'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(
        LinkSchema(partial=True),
        description='Contains a result string for the link requested.')
    async def get_operation_link_result(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        result = await self._api_manager.get_operation_link_result(
            operation_id, link_id, access)
        return web.json_response(result)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Update the specified link within an operation',
        description=
        'Update the `command` (String) or `status` (Integer) field within the link with '
        'the provided  `link_id` (String UUID) from the operation with the given '
        'operation `id` (String UUID).',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'String UUID of the Operation containing desired link.'
        }, {
            'in':
            'path',
            'name':
            'link_id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'String UUID of the Link with the above operation.'
        }])
    @aiohttp_apispec.request_schema(
        LinkSchema(partial=True, only=['command', 'status']))
    @aiohttp_apispec.response_schema(
        LinkSchema,
        description='The updated link after a successful `PATCH` request.')
    async def update_operation_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        link_id = request.match_info.get('link_id')
        access = await self.get_request_permissions(request)
        data = await request.json()
        link = await self._api_manager.update_operation_link(
            operation_id, link_id, data, access)
        return web.json_response(link)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Creates a potential Link',
        description=
        'Creates a potential link to be executed by an agent. Create a potential Link using '
        'the format provided in the `LinkSchema`. The request body requires `paw`, '
        '`executor`, and `ability`.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the operation object for the link to be created on.'
        }])
    @aiohttp_apispec.request_schema(LinkSchema)
    @aiohttp_apispec.response_schema(
        LinkSchema,
        description='Response contains the newly assigned Link object.')
    async def create_potential_link(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        data = await request.json()
        potential_link = await self._api_manager.create_potential_link(
            operation_id, data, access)
        return web.json_response(potential_link)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary='Retrieve potential links for an operation.',
        description=
        'Retrieve all potential links for an operation based on the operation id (String '
        'UUID).  Use fields from the `BaseGetAllQuerySchema` in the request body to add '
        '`include`, `exclude`, and `sort` filters.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'UUID of the operation object to retrieve links for.'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetAllQuerySchema)
    @aiohttp_apispec.response_schema(
        LinkSchema(many=True, partial=True),
        description=
        'Response contains a list of link objects for the requested id.')
    async def get_potential_links(self, request: web.Request):
        operation_id = request.match_info.get('id')
        access = await self.get_request_permissions(request)
        potential_links = await self._api_manager.get_potential_links(
            operation_id, access)
        return web.json_response(potential_links)

    @aiohttp_apispec.docs(
        tags=['operations'],
        summary=
        'Retrieve potential links for an operation filterd by agent paw (id)',
        description=
        'Retrieve all potential links for an operation-agent pair based on the operation id (String '
        'UUID) and the agent paw (id) (String).  Use fields from the `BaseGetAllQuerySchema` '
        'in the request body to add `include`, `exclude`, and `sort` filters.',
        parameters=[{
            'in':
            'path',
            'name':
            'id',
            'schema': {
                'type': 'string'
            },
            'required':
            'true',
            'description':
            'String UUID of the Operation containing desired links.'
        }, {
            'in': 'path',
            'name': 'paw',
            'schema': {
                'type': 'string'
            },
            'required': 'true',
            'description': 'Agent paw for the specified operation.'
        }])
    @aiohttp_apispec.querystring_schema(BaseGetOneQuerySchema)
    @aiohttp_apispec.response_schema(
        LinkSchema(partial=True),
        description=
        'All potential links for operation and the specified agent paw.')
    async def get_potential_links_by_paw(self, request: web.Request):
        operation_id = request.match_info.get('id')
        paw = request.match_info.get('paw')
        access = await self.get_request_permissions(request)
        potential_links = await self._api_manager.get_potential_links(
            operation_id, access, paw)
        return web.json_response(potential_links)

    async def create_object(self, request: web.Request):
        data = await request.json()
        await self._error_if_object_with_id_exists(data.get(self.id_property))
        access = await self.get_request_permissions(request)
        return await self._api_manager.create_object_from_schema(
            self.schema, data, access)

    async def update_object(self, request: web.Request):
        data, access, obj_id, query, search = await self._parse_common_data_from_request(
            request)
        obj = await self._api_manager.find_and_update_object(
            self.ram_key, data, search)
        if not obj:
            raise JsonHttpNotFound(
                f'{self.description.capitalize()} not found: {obj_id}')
        return obj

    async def _read_output_parameter_(self, request: web.Request):
        raw_body = await request.read()
        output = False
        if raw_body:
            output = json.loads(raw_body).get('enable_agent_output', False)
        return output