Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implementation of new JSON responses #28

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions sner/agent/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -197,7 +197,12 @@ def get_assignment(self):
try:
response = self.call_api(self.get_assignment_url, self.get_assignment_params)
response.raise_for_status()

assignment = response.json()

if 'data' in assignment:
assignment = assignment['data']

if not assignment: # response-nowork
self.log.debug('get_assignment response-nowork')
if self.oneshot: # pylint: disable=no-else-break ; improves readability for following pragma
Expand Down
22 changes: 13 additions & 9 deletions sner/server/api/views.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from datetime import datetime, timedelta
from http import HTTPStatus

from flask import current_app, jsonify, Response
from flask import current_app, Response
from flask_login import current_user
from flask_smorest import Blueprint
from sqlalchemy import func, or_
Expand All @@ -29,8 +29,7 @@
from sner.server.scheduler.core import SchedulerService, SchedulerServiceBusyException
from sner.server.scheduler.models import Job, Queue, Target
from sner.server.storage.models import Host, Note, Service, Vuln
from sner.server.utils import filter_query

from sner.server.utils import filter_query, json_data_response, json_error_response

blueprint = Blueprint('api', __name__) # pylint: disable=invalid-name

Expand All @@ -44,11 +43,15 @@ def v2_scheduler_job_assign_route(args):

try:
resp = SchedulerService.job_assign(args.get('queue'), args.get('caps', []))

if 'id' in resp:
current_app.logger.info(f'api.scheduler job assign {resp.get("id")}')
except SchedulerServiceBusyException:
resp = {} # nowork
return resp

serialized_data = JobAssignmentSchema().dump(resp)

return json_data_response(serialized_data)


@blueprint.route('/v2/scheduler/job/output', methods=['POST'])
Expand All @@ -60,22 +63,23 @@ def v2_scheduler_job_output_route(args):
try:
output = b64decode(args['output'])
except binascii.Error:
return jsonify({'message': 'invalid request'}), HTTPStatus.BAD_REQUEST
return json_error_response('invalid request', HTTPStatus.BAD_REQUEST)

job = Job.query.filter(Job.id == args['id'], Job.retval == None).one_or_none() # noqa: E711 pylint: disable=singleton-comparison
if not job:
# invalid/repeated requests are silently discarded, agent would delete working data
# on it's side as well
return jsonify({'message': 'discard job'})
return json_data_response({'message': 'discard job'})

try:
job_id = job.id
SchedulerService.job_output(job, args['retval'], output)
except SchedulerServiceBusyException:
return jsonify({'message': 'server busy'}), HTTPStatus.TOO_MANY_REQUESTS
return json_error_response('server busy', HTTPStatus.TOO_MANY_REQUESTS)

current_app.logger.info(f'api.scheduler job output {job_id}')
return jsonify({'message': 'success'})

return json_data_response({'message': 'success'})


@blueprint.route('/v2/stats/prometheus')
Expand Down Expand Up @@ -171,7 +175,7 @@ def v2_public_storage_servicelist_route(args):
).filter(or_(*restrict))

if not (query := filter_query(query, args.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

current_app.logger.info(f'api.public storage servicelist {args}')
return query.all()
10 changes: 5 additions & 5 deletions sner/server/auth/views/user.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from sner.server.extensions import db
from sner.server.forms import ButtonForm
from sner.server.password_supervisor import PasswordSupervisor as PWS
from sner.server.utils import filter_query
from sner.server.utils import filter_query, json_data_response, json_error_response


@blueprint.route('/user/list')
Expand All @@ -43,7 +43,7 @@ def user_list_json_route():
]
query = db.session.query().select_from(User)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

users = DataTables(request.values.to_dict(), query, columns).output_result()
return jsonify(users)
Expand Down Expand Up @@ -111,10 +111,10 @@ def user_apikey_route(user_id, action):
if user and form.validate_on_submit():
if action == 'generate':
apikey = UserManager.apikey_generate(user)
return jsonify({'message': 'Apikey operation', 'detail': f'New apikey generated: {apikey}'}), HTTPStatus.OK
return json_data_response({'message': f'New apikey generated: {apikey}'})

if action == 'revoke':
UserManager.apikey_revoke(user)
return jsonify({'message': 'Apikey operation', 'detail': 'Apikey revoked'}), HTTPStatus.OK
return json_data_response({'message': 'Apikey revoked'})

return jsonify({'message': 'Apikey operation', 'detail': 'Invalid request'}), HTTPStatus.BAD_REQUEST
return json_error_response('Invalid request', HTTPStatus.BAD_REQUEST)
10 changes: 5 additions & 5 deletions sner/server/scheduler/views/job.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from http import HTTPStatus

from datatables import ColumnDT, DataTables
from flask import jsonify, redirect, render_template, request, Response, url_for
from flask import redirect, render_template, request, Response, url_for
from sqlalchemy import func, literal_column

from sner.server.auth.core import session_required
Expand All @@ -17,7 +17,7 @@
from sner.server.scheduler.core import JobManager
from sner.server.scheduler.models import Job, Queue
from sner.server.scheduler.views import blueprint
from sner.server.utils import filter_query, SnerJSONEncoder
from sner.server.utils import filter_query, SnerJSONEncoder, json_error_response


@blueprint.route('/job/list')
Expand Down Expand Up @@ -45,7 +45,7 @@ def job_list_json_route():
]
query = db.session.query().select_from(Job).outerjoin(Queue)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

jobs = DataTables(request.values.to_dict(), query, columns).output_result()
return Response(json.dumps(jobs, cls=SnerJSONEncoder), mimetype='application/json')
Expand All @@ -63,7 +63,7 @@ def job_delete_route(job_id):
JobManager.delete(Job.query.get(job_id))
return redirect(url_for('scheduler.job_list_route'))
except RuntimeError as exc:
return jsonify({'message': f'Failed: {exc}'}), HTTPStatus.INTERNAL_SERVER_ERROR
return json_error_response(f'Failed: {exc}', HTTPStatus.INTERNAL_SERVER_ERROR)

return render_template('button-delete.html', form=form)

Expand All @@ -79,7 +79,7 @@ def job_reconcile_route(job_id):
JobManager.reconcile(Job.query.get(job_id))
return redirect(url_for('scheduler.job_list_route'))
except RuntimeError as exc:
return jsonify({'message': f'Failed: {exc}'}), HTTPStatus.INTERNAL_SERVER_ERROR
return json_error_response(f'Failed: {exc}', HTTPStatus.INTERNAL_SERVER_ERROR)

return render_template('button-generic.html', form=form)

Expand Down
8 changes: 4 additions & 4 deletions sner/server/scheduler/views/queue.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
from sner.server.scheduler.forms import QueueEnqueueForm, QueueForm
from sner.server.scheduler.models import Job, Queue, Target
from sner.server.scheduler.views import blueprint
from sner.server.utils import filter_query
from sner.server.utils import filter_query, json_error_response


@blueprint.route('/queue/list', methods=['GET'])
Expand Down Expand Up @@ -50,7 +50,7 @@ def queue_list_json_route():
.outerjoin(query_nr_targets, Queue.id == query_nr_targets.c.queue_id) \
.outerjoin(query_nr_jobs, Queue.id == query_nr_jobs.c.queue_id)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

queues = DataTables(request.values.to_dict(), query, columns).output_result()
return jsonify(queues)
Expand Down Expand Up @@ -129,7 +129,7 @@ def queue_prune_route(queue_id):
QueueManager.prune(Queue.query.get(queue_id))
return redirect(url_for('scheduler.queue_list_route'))
except RuntimeError as exc:
return jsonify({'message': f'Failed: {exc}'}), HTTPStatus.INTERNAL_SERVER_ERROR
return json_error_response(f'Failed: {exc}', HTTPStatus.INTERNAL_SERVER_ERROR)

return render_template('button-generic.html', form=form, button_caption='Prune')

Expand All @@ -146,6 +146,6 @@ def queue_delete_route(queue_id):
QueueManager.delete(Queue.query.get(queue_id))
return redirect(url_for('scheduler.queue_list_route'))
except RuntimeError as exc:
return jsonify({'message': f'Failed: {exc}'}), HTTPStatus.INTERNAL_SERVER_ERROR
return json_error_response(f'Failed: {exc}', HTTPStatus.INTERNAL_SERVER_ERROR)

return render_template('button-delete.html', form=form)
2 changes: 1 addition & 1 deletion sner/server/static/sner/sner.js
Original file line number Diff line number Diff line change
Expand Up @@ -312,7 +312,7 @@ class SnerModule {
data['csrf_token'] = $('meta[name="csrf-token"]').attr('content');
return $.ajax({"url": url,"type": "POST", "data": data})
.fail(function(xhr, status, exception) {
toastr.error(xhr.hasOwnProperty('responseJSON') ? xhr.responseJSON['message'] : 'Request failed');
toastr.error(xhr.hasOwnProperty('responseJSON') ? xhr.responseJSON['error']['message'] : 'Request failed');
});
}

Expand Down
6 changes: 3 additions & 3 deletions sner/server/storage/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from http import HTTPStatus
from io import StringIO

from flask import current_app, jsonify, render_template
from flask import current_app, render_template
from pytimeparse import parse as timeparse
from sqlalchemy import case, delete, func, or_, not_, select, update
from sqlalchemy.sql.functions import coalesce
Expand All @@ -18,7 +18,7 @@
from sner.server.extensions import db
from sner.server.storage.forms import AnnotateForm, TagMultiidForm
from sner.server.storage.models import Host, Note, Service, Vuln
from sner.server.utils import filter_query, windowed_query
from sner.server.utils import filter_query, windowed_query, json_error_response


def get_related_models(model_name, model_id):
Expand Down Expand Up @@ -75,7 +75,7 @@ def tag_model_multiid(model_class):
db.session.commit()
return '', HTTPStatus.OK

return jsonify({'message': 'Invalid form submitted.'}), HTTPStatus.BAD_REQUEST
return json_error_response('Invalid form submitted.', HTTPStatus.BAD_REQUEST)


def url_for_ref(ref):
Expand Down
6 changes: 3 additions & 3 deletions sner/server/storage/views/host.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import json
from datatables import ColumnDT, DataTables
from flask import jsonify, redirect, render_template, request, Response, url_for
from flask import redirect, render_template, request, Response, url_for
from sqlalchemy import func, literal_column

from sner.server.auth.core import session_required
Expand All @@ -17,7 +17,7 @@
from sner.server.storage.forms import HostForm
from sner.server.storage.models import Host, Note, Service, Vuln
from sner.server.storage.views import blueprint
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url, json_error_response


@blueprint.route('/host/list')
Expand Down Expand Up @@ -56,7 +56,7 @@ def host_list_json_route():
.outerjoin(query_cnt_vulns, Host.id == query_cnt_vulns.c.host_id) \
.outerjoin(query_cnt_notes, Host.id == query_cnt_notes.c.host_id)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

hosts = DataTables(request.values.to_dict(), query, columns).output_result()
return Response(json.dumps(hosts, cls=SnerJSONEncoder), mimetype='application/json')
Expand Down
6 changes: 3 additions & 3 deletions sner/server/storage/views/note.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

import json
from datatables import ColumnDT, DataTables
from flask import jsonify, redirect, render_template, request, Response, url_for
from flask import redirect, render_template, request, Response, url_for
from sqlalchemy import func, literal_column

from sner.server.auth.core import session_required
Expand All @@ -17,7 +17,7 @@
from sner.server.storage.forms import NoteForm
from sner.server.storage.models import Host, Note, Service
from sner.server.storage.views import blueprint
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url, json_error_response


@blueprint.route('/note/list')
Expand Down Expand Up @@ -54,7 +54,7 @@ def note_list_json_route():
]
query = db.session.query().select_from(Note).outerjoin(Host, Note.host_id == Host.id).outerjoin(Service, Note.service_id == Service.id)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

notes = DataTables(request.values.to_dict(), query, columns).output_result()
return Response(json.dumps(notes, cls=SnerJSONEncoder), mimetype='application/json')
Expand Down
5 changes: 3 additions & 2 deletions sner/server/storage/views/quickjump.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from sner.server.storage.forms import QuickjumpForm
from sner.server.storage.models import Host
from sner.server.storage.views import blueprint
from sner.server.utils import json_error_response


AUTOCOMPLETE_LIMIT = 10
Expand All @@ -39,9 +40,9 @@ def quickjump_route():
if form.quickjump.data.isnumeric():
return jsonify({'message': 'success', 'url': url_for('storage.service_list_route', filter=f"Service.port==\"{form.quickjump.data}\"")})

return jsonify({'message': 'Not found'}), HTTPStatus.NOT_FOUND
return json_error_response('Not found', HTTPStatus.NOT_FOUND)

return jsonify({'message': 'Invalid request'}), HTTPStatus.BAD_REQUEST
return json_error_response('Invalid request', HTTPStatus.BAD_REQUEST)


@blueprint.route('/quickjump_autocomplete')
Expand Down
6 changes: 3 additions & 3 deletions sner/server/storage/views/service.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
from sner.server.storage.forms import ServiceForm
from sner.server.storage.models import Host, Service
from sner.server.storage.views import blueprint
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url, json_error_response


def service_info_column(crop):
Expand Down Expand Up @@ -62,7 +62,7 @@ def service_list_json_route():
]
query = db.session.query().select_from(Service).outerjoin(Host)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

services = DataTables(request.values.to_dict(), query, columns).output_result()
return Response(json.dumps(services, cls=SnerJSONEncoder), mimetype='application/json')
Expand Down Expand Up @@ -147,7 +147,7 @@ def service_grouped_json_route():
# join allows filter over host attrs
query = db.session.query().select_from(Service).join(Host).group_by(info_column)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

services = DataTables(request.values.to_dict(), query, columns).output_result()
return jsonify(services)
10 changes: 5 additions & 5 deletions sner/server/storage/views/vuln.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
from http import HTTPStatus

from datatables import ColumnDT, DataTables
from flask import jsonify, redirect, render_template, request, Response, url_for
from flask import redirect, render_template, request, Response, url_for
from sqlalchemy import func, literal_column

from sner.server.auth.core import session_required
Expand All @@ -18,7 +18,7 @@
from sner.server.storage.forms import MultiidForm, VulnForm
from sner.server.storage.models import Host, Service, Vuln
from sner.server.storage.views import blueprint
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url
from sner.server.utils import filter_query, relative_referrer, SnerJSONEncoder, valid_next_url, json_error_response


@blueprint.route('/vuln/list')
Expand Down Expand Up @@ -58,7 +58,7 @@ def vuln_list_json_route():
]
query = db.session.query().select_from(Vuln).outerjoin(Host, Vuln.host_id == Host.id).outerjoin(Service, Vuln.service_id == Service.id)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

vulns = DataTables(request.values.to_dict(), query, columns).output_result()
return Response(json.dumps(vulns, cls=SnerJSONEncoder), mimetype='application/json')
Expand Down Expand Up @@ -141,7 +141,7 @@ def vuln_delete_multiid_route():
db.session.expire_all()
return '', HTTPStatus.OK

return jsonify({'message': 'Invalid form submitted.'}), HTTPStatus.BAD_REQUEST
return json_error_response('Invalid form submitted.', HTTPStatus.BAD_REQUEST)


@blueprint.route('/vuln/tag_multiid', methods=['POST'])
Expand Down Expand Up @@ -173,7 +173,7 @@ def vuln_grouped_json_route():
# join allows filter over host attrs
query = db.session.query().select_from(Vuln).join(Host).group_by(Vuln.name, Vuln.severity, Vuln.tags)
if not (query := filter_query(query, request.values.get('filter'))):
return jsonify({'message': 'Failed to filter query'}), HTTPStatus.BAD_REQUEST
return json_error_response('Failed to filter query', HTTPStatus.BAD_REQUEST)

vulns = DataTables(request.values.to_dict(), query, columns).output_result()
return Response(json.dumps(vulns, cls=SnerJSONEncoder), mimetype='application/json')
Expand Down
2 changes: 1 addition & 1 deletion sner/server/templates/auth/user/list.html
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
<script type="text/javascript">
function action_userapikey(event) {
Sner.submit_form(event.target.closest('a').getAttribute('data-url'))
.done(function(data, textStatus, jqXHR) { Sner.modal(data.message, data.detail); })
.done(function(res, textStatus, jqXHR) { Sner.modal('Apikey operation', res.data.message); })
.always(function() { event.data.dt.draw(); });
}

Expand Down
Loading