fix: better CRUD API + new method to retrieve clients

This commit is contained in:
ziirish 2015-10-14 22:17:14 +02:00
parent 62a56af76b
commit 3de063a036
12 changed files with 163 additions and 90 deletions

View file

@ -127,6 +127,7 @@ class BUIAgent(BUIlogging, Dummy):
'store_conf_srv': self.backend.store_conf_srv,
'expand_path': self.backend.expand_path,
'delete_client': self.backend.delete_client,
'clients_list': self.backend.clients_list,
'get_parser_attr': self.backend.get_parser_attr
}

View file

@ -92,10 +92,10 @@ class ClientTree(Resource):
return jsonify(results=j)
@api.resource('/api/client-stat.json/<name>',
'/api/<server>/client-stat.json/<name>',
'/api/client-stat.json/<name>/<int:backup>',
'/api/<server>/client-stat.json/<name>/<int:backup>',
@api.resource('/api/client-stats.json/<name>',
'/api/<server>/client-stats.json/<name>',
'/api/client-stats.json/<name>/<int:backup>',
'/api/<server>/client-stats.json/<name>/<int:backup>',
endpoint='api.client_stats')
class ClientStats(Resource):
"""The :class:`burpui.api.client.ClientStats` resource allows you to

View file

@ -20,7 +20,7 @@ class ServersStats(Resource):
@login_required
def get(self):
r = []
if hasattr(api.bui.cli, 'servers'):
if hasattr(api.bui.cli, 'servers'): # pragma: no cover
check = False
allowed = []
if (api.bui.acl and not
@ -73,7 +73,7 @@ class Live(Resource):
l = (api.bui.cli.is_one_backup_running(server))[server]
else:
l = api.bui.cli.is_one_backup_running()
if isinstance(l, dict):
if isinstance(l, dict): # pragma: no cover
for (k, a) in iteritems(l):
for c in a:
s = {}
@ -84,7 +84,7 @@ class Live(Resource):
except BUIserverException:
s['status'] = []
r.append(s)
else:
else: # pragma: no cover
for c in l:
s = {}
s['client'] = c

View file

@ -13,16 +13,17 @@ from burpui.api import api
from flask.ext.restful import reqparse, abort, Resource
from flask.ext.login import current_user, login_required
from flask import jsonify, flash, request, redirect, url_for
from werkzeug.datastructures import ImmutableMultiDict
if sys.version_info >= (3, 0):
from urllib.parse import unquote
else:
from urllib import unquote
@api.resource('/api/server-config',
'/api/<server>/server-config',
'/api/server-config/<path:conf>',
'/api/<server>/server-config/<path:conf>',
@api.resource('/api/settings/server-config',
'/api/<server>/settings/server-config',
'/api/settings/server-config/<path:conf>',
'/api/<server>/settings/server-config/<path:conf>',
endpoint='api.server_settings')
class ServerSettings(Resource):
"""The :class:`burpui.api.settings.ServerSettings` resource allows you to
@ -31,6 +32,11 @@ class ServerSettings(Resource):
This resource is part of the :mod:`burpui.api.settings` module.
"""
@login_required
def post(self, conf=None, server=None):
noti = api.bui.cli.store_conf_srv(request.form, conf, server)
return jsonify(notif=noti)
@login_required
def get(self, conf=None, server=None):
"""**GET** method provided by the webservice.
@ -182,13 +188,29 @@ class ServerSettings(Resource):
defaults=api.bui.cli.get_parser_attr('defaults', server))
@api.resource('/api/<client>/client-config',
'/api/<client>/client-config/<path:conf>',
'/api/<server>/<client>/client-config',
'/api/<server>/<client>/client-config/<path:conf>',
@api.resource('/api/settings/clients.json',
'/api/<server>/settings/clients.json',
endpoint='api.clients_list')
class ClientsList(Resource):
@login_required
def get(self, server=None):
res = api.bui.cli.clients_list(server)
return jsonify(result=res)
@api.resource('/api/settings/<client>/client-config',
'/api/settings/<client>/client-config/<path:conf>',
'/api/<server>/settings/<client>/client-config',
'/api/<server>/settings/<client>/client-config/<path:conf>',
endpoint='api.client_settings')
class ClientSettings(Resource):
@login_required
def post(self, server=None, client=None, conf=None):
noti = api.bui.cli.store_conf_cli(request.form, client, conf, server)
return jsonify(notif=noti)
@login_required
def get(self, server=None, client=None, conf=None):
# Only the admin can edit the configuration
@ -212,8 +234,8 @@ class ClientSettings(Resource):
defaults=api.bui.cli.get_parser_attr('defaults', server))
@api.resource('/api/new-client',
'/api/<server>/new-client',
@api.resource('/api/settings/new-client',
'/api/<server>/settings/new-client',
endpoint='api.new_client')
class NewClient(Resource):
@ -222,7 +244,7 @@ class NewClient(Resource):
self.parser.add_argument('newclient', type=str)
@login_required
def post(self, server=None):
def put(self, server=None):
# Only the admin can edit the configuration
if (api.bui.acl and not
api.bui.acl.is_admin(current_user.get_id())):
@ -230,19 +252,23 @@ class NewClient(Resource):
newclient = self.parser.parse_args()['newclient']
if not newclient:
flash('No client name provided', 'danger')
return redirect(request.referrer)
abort(500, message='No client name provided')
# clientconfdir = api.bui.cli.get_parser_attr('clientconfdir', server)
# if not clientconfdir:
# flash('Could not proceed, no \'clientconfdir\' find', 'warning')
# return redirect(request.referrer)
return redirect(url_for('view.cli_settings', server=server, client=newclient))
noti = api.bui.cli.store_conf_cli(ImmutableMultiDict(), newclient, None, server)
if server:
noti.append([3, '<a href="{}">Click here</a> to edit \'{}\' configuration'.format(url_for('view.cli_settings', server=server, client=newclient), newclient)])
else:
noti.append([3, '<a href="{}">Click here</a> to edit \'{}\' configuration'.format(url_for('view.cli_settings', client=newclient), newclient)])
return {'notif': noti}, 201
@api.resource('/api/path-expander',
'/api/<server>/path-expander',
'/api/path-expander/<client>',
'/api/<server>/path-expander/<client>',
@api.resource('/api/settings/path-expander',
'/api/<server>/settings/path-expander',
'/api/settings/path-expander/<client>',
'/api/<server>/settings/path-expander/<client>',
endpoint='api.path_expander')
class PathExpander(Resource):
@ -251,7 +277,7 @@ class PathExpander(Resource):
self.parser.add_argument('path')
@login_required
def post(self, server=None, client=None):
def get(self, server=None, client=None):
# Only the admin can edit the configuration
if (api.bui.acl and not
api.bui.acl.is_admin(current_user.get_id())):
@ -266,19 +292,19 @@ class PathExpander(Resource):
return jsonify(result=paths)
@api.resource('/api/delete-client',
'/api/<server>/delete-client',
'/api/delete-client/<client>',
'/api/<server>/delete-client/<client>',
@api.resource('/api/settings/delete-client',
'/api/<server>/settings/delete-client',
'/api/settings/delete-client/<client>',
'/api/<server>/settings/delete-client/<client>',
endpoint='api.delete_client')
class DeleteClient(Resource):
@login_required
def post(self, server=None, client=None):
def delete(self, server=None, client=None):
# Only the admin can edit the configuration
if (api.bui.acl and not
api.bui.acl.is_admin(current_user.get_id())):
noti = [2, 'Sorry, you don\'t have rights to access the setting panel']
return jsonify(notif=noti)
return jsonify(notif=api.bui.cli.delete_client(client, server))
return {'notif': api.bui.cli.delete_client(client, server)}, 200

View file

@ -551,6 +551,15 @@ class BUIbackend(BUIlogging):
"""
raise NotImplementedError("Sorry, the current Backend does not implement this method!")
def clients_list(self, agent=None):
"""The :func:`burpui.misc.backend.interface.BUIbackend.clients_list`
function is used to retrieve a list of clients with their configuration
file.
:returns: A list of clients with their configuration file
"""
raise NotImplementedError("Sorry, the current Backend does not implement this method!")
def delete_client(self, client=None, agent=None):
"""The :func:`burpui.misc.backend.interface.BUIbackend.delete_client`
function is used to delete a client from burp's configuration.

View file

@ -184,6 +184,10 @@ class Burp(BUIbackend):
"""See :func:`burpui.misc.backend.interface.BUIbackend.delete_client`"""
return self.servers[agent].delete_client(client)
def clients_list(self, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.clients_list`"""
return self.servers[agent].clients_list()
def get_parser_attr(self, attr=None, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.get_parser_attr`"""
return self.servers[agent].get_parser_attr(attr)
@ -425,6 +429,11 @@ class NClient(BUIbackend):
data = {'func': 'delete_client', 'args': {'client': client}}
return json.loads(self.do_command(data))
def clients_list(self, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.clients_list`"""
data = {'func': 'clients_list', 'args': None}
return json.loads(self.do_command(data))
def get_parser_attr(self, attr=None, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.get_parser_attr`"""
data = {'func': 'get_parser_attr', 'args': {'attr': attr}}

View file

@ -692,6 +692,7 @@ class Parser(BUIparser):
def _list_clients(self):
if not self.clientconfdir:
return []
res = []
for f in os.listdir(self.clientconfdir):
ff = os.path.join(self.clientconfdir, f)
@ -700,6 +701,14 @@ class Parser(BUIparser):
return res
def list_clients(self):
"""See :func:`burpui.misc.parser.interface.BUIparser.list_clients`"""
self.read_server_conf()
if not self.clientconfdir:
return []
return self._list_clients()
def store_client_conf(self, data, client=None, conf=None):
"""See :func:`burpui.misc.parser.interface.BUIparser.store_client_conf`"""
if conf and not conf.startswith('/'):

View file

@ -179,6 +179,14 @@ class BUIparser(BUIlogging):
"""
raise NotImplementedError("Sorry, the current Parser does not implement this method!")
def list_clients(self):
""":func:`burpui.misc.parser.interface.BUIparser.list_clients` is used
to retrieve a list of clients with their configuration file.
:returns: A list of clients with their configuration file
"""
raise NotImplementedError("Sorry, the current Parser does not implement this method!")
def remove_client(self, client=None):
""":func:`burpui.misc.parser.interface.BUIparser.remove_client` is used
to delete a client from burp's configuration.

View file

@ -56,10 +56,10 @@ And here is the main site
"""
@view.route('/settings', methods=['GET', 'POST'])
@view.route('/settings/<path:conf>', methods=['GET', 'POST'])
@view.route('/<server>/settings', methods=['GET', 'POST'])
@view.route('/<server>/settings/<path:conf>', methods=['GET', 'POST'])
@view.route('/settings')
@view.route('/settings/<path:conf>')
@view.route('/<server>/settings')
@view.route('/<server>/settings/<path:conf>')
@login_required
def settings(server=None, conf=None):
# Only the admin can edit the configuration
@ -72,18 +72,15 @@ def settings(server=None, conf=None):
pass
if not server:
server = request.args.get('server')
if request.method == 'POST':
noti = view.bui.cli.store_conf_srv(request.form, conf, server)
return jsonify(notif=noti)
return render_template('settings.html', settings=True, server=server, conf=conf)
@view.route('/client/client-settings', methods=['GET', 'POST'])
@view.route('/<client>/client-settings', methods=['GET', 'POST'])
@view.route('/<client>/client-settings/<path:conf>', methods=['GET', 'POST'])
@view.route('/<server>/client/client-settings', methods=['GET', 'POST'])
@view.route('/<server>/<client>/client-settings', methods=['GET', 'POST'])
@view.route('/<server>/<client>/client-settings/<path:conf>', methods=['GET', 'POST'])
@view.route('/client/client-settings')
@view.route('/<client>/client-settings')
@view.route('/<client>/client-settings/<path:conf>')
@view.route('/<server>/client/client-settings')
@view.route('/<server>/<client>/client-settings')
@view.route('/<server>/<client>/client-settings/<path:conf>')
@login_required
def cli_settings(server=None, client=None, conf=None):
# Only the admin can edit the configuration
@ -98,9 +95,6 @@ def cli_settings(server=None, client=None, conf=None):
client = request.args.get('client')
if not server:
server = request.args.get('server')
if request.method == 'POST':
noti = view.bui.cli.store_conf_cli(request.form, client, conf, server)
return jsonify(notif=noti)
return render_template('settings.html', settings=True, client=client, server=server, conf=conf)

View file

@ -193,20 +193,11 @@ app.controller('ConfigCtrl', function($scope, $http) {
submit.attr('disabled', true);
/* submit the data */
$.ajax({
url: form.attr('action'),
type: form.attr('method'),
data: form.serialize()
url: form.attr('action'),
type: 'POST',
data: form.serialize()
}).fail(function(xhr, stat, err) {
/* display errors if something went wrong HTTP side */
var msg = '<strong>ERROR:</strong> ';
if (stat && err) {
msg += '<p>'+stat+'</p><pre>'+err+'</pre>';
} else if (stat) {
msg += '<p>'+stat+'</p>';
} else if (err) {
msg += '<pre>'+err+'</pre>';
}
notif(2, msg, 10000);
$scope.showError(stat, err);
}).done(function(data) {
/* The server answered correctly but some errors may have occurred server
* side so we display them */
@ -218,6 +209,7 @@ app.controller('ConfigCtrl', function($scope, $http) {
}
$scope.setSettings.$setPristine();
$scope.changed = false;
$scope.getClientsList();
}).always(function() {
/* reset the submit button state */
submit.text(sav);
@ -229,6 +221,18 @@ app.controller('ConfigCtrl', function($scope, $http) {
});
}
};
$scope.showError = function(stat, err) {
/* display errors if something went wrong HTTP side */
var msg = '<strong>ERROR:</strong> ';
if (stat && err) {
msg += '<p>'+stat+'</p><pre>'+err+'</pre>';
} else if (stat) {
msg += '<p>'+stat+'</p>';
} else if (err) {
msg += '<pre>'+err+'</pre>';
}
notif(2, msg, 10000);
};
$scope.remove = function(key, index) {
if (!$scope.old[key]) {
$scope.old[key] = {};
@ -329,20 +333,11 @@ app.controller('ConfigCtrl', function($scope, $http) {
{% endif -%}
$scope.inc_invalid = {};
$.ajax({
url: api,
type: 'POST',
data: {'path': path}
url: api,
type: 'GET',
data: {'path': path}
}).fail(function(xhr, stat, err) {
/* display errors if something went wrong HTTP side */
var msg = '<strong>ERROR:</strong> ';
if (stat && err) {
msg += '<p>'+stat+'</p><pre>'+err+'</pre>';
} else if (stat) {
msg += '<p>'+stat+'</p>';
} else if (err) {
msg += '<pre>'+err+'</pre>';
}
notif(2, msg, 10000);
$scope.showError(stat, err);
}).done(function(data) {
/* The server answered correctly but some errors may have occurred server
* side so we display them */
@ -357,33 +352,55 @@ app.controller('ConfigCtrl', function($scope, $http) {
}
});
};
$scope.getClientsList = function() {
api = '{{ url_for("api.clients_list", server=server) }}';
$.ajax({
url: api,
type: 'GET'
}).done(function(data) {
$scope.clients = data.result;
});
};
$scope.deleteClient = function() {
api = '{{ url_for("api.delete_client", client=client, server=server) }}';
$.ajax({
url: api,
type: 'POST'
url: api,
type: 'DELETE'
}).fail(function(xhr, stat, err) {
/* display errors if something went wrong HTTP side */
var msg = '<strong>ERROR:</strong> ';
if (stat && err) {
msg += '<p>'+stat+'</p><pre>'+err+'</pre>';
} else if (stat) {
msg += '<p>'+stat+'</p>';
} else if (err) {
msg += '<pre>'+err+'</pre>';
}
notif(2, msg, 10000);
$scope.showError(stat, err);
}).done(function(data) {
/* The server answered correctly but some errors may have occurred server
* side so we display them */
if (data.notif) {
notif(data.notif[0], data.notif[1])
notif(data.notif[0], data.notif[1]);
if (data.notif[0] == 0) {
document.location = '{{ url_for("view.settings", server=server) }}';
}
}
});
};
$scope.createClient = function(e) {
/* we disable the 'real' form submission */
e.preventDefault();
var form = $(e.target);
$.ajax({
url: form.attr('action'),
type: 'PUT',
data: form.serialize()
}).fail(function(xhr, stat, err) {
$scope.showError(stat, err);
}).done(function(data) {
/* The server answered correctly but some errors may have occurred server
* side so we display them */
if (data.notif) {
notif(data.notif[0][0], data.notif[0][1]);
if (data.notif[0][0] == 0) {
$scope.getClientsList();
notif(data.notif[1][0], data.notif[1][1], 20000);
}
}
});
};
/* These callbacks expand/reduce the input for a better readability */
$scope.focusIn = function(ev) {
el = $( ev.target ).parent();

View file

@ -23,9 +23,9 @@
<div id="settings-panel" class="form-container" style="display:none;">
{% if client -%}
<form class="form-horizontal" action="{{ url_for('view.cli_settings', client=client, conf=conf, server=server) }}" method="POST" ng-submit="submit($event)" name="setSettings" onbeforeunload>
<form class="form-horizontal" action="{{ url_for('api.client_settings', client=client, conf=conf, server=server) }}" method="POST" ng-submit="submit($event)" name="setSettings" onbeforeunload>
{% else -%}
<form class="form-horizontal" action="{{ url_for('view.settings', conf=conf, server=server) }}" method="POST" ng-submit="submit($event)" name="setSettings" onbeforeunload>
<form class="form-horizontal" action="{{ url_for('api.server_settings', conf=conf, server=server) }}" method="POST" ng-submit="submit($event)" name="setSettings" onbeforeunload>
{% endif -%}
{# From here, the jinja syntax is escaped because we use the angularjs syntax #}
{% raw %}

View file

@ -26,7 +26,7 @@
</ul>
<ul class="nav nav-sidebar">
<li>
<form action="{{ url_for('api.new_client') }}" method="POST">
<form action="{{ url_for('api.new_client') }}" method="POST" ng-submit="createClient($event)">
<div class="input-group">
<input class="form-control" type="text" name="newclient" id="newclient" placeholder="Create new client">
<span class="input-group-btn">