Commit 0596da43 authored by Benjamin "Ziirish" SANS's avatar Benjamin "Ziirish" SANS
Browse files

Merge branch '203-implement-backups-deletion' into 'master'

add: support backups deletion

Closes #203

See merge request !73
parents 0ec69d66 788d7e71
......@@ -6,8 +6,9 @@ Current
- **Breaking**: the *BASIC* `ACL` engine will now grant users on all agents if they are not explicitly defined
- Add: new plugins system to allow users to write their own modules
- Add: `support new burp counters <https://git.ziirish.me/ziirish/burp-ui/issues/219>`_
- Add: `backups deletion <https://git.ziirish.me/ziirish/burp-ui/issues/203>`_
- Add: `record login failure attempt <https://git.ziirish.me/ziirish/burp-ui/issues/214>`_
- Add: `support new burp counters <https://git.ziirish.me/ziirish/burp-ui/issues/219>`_
0.5.1 (05/26/2017)
------------------
......
......@@ -782,7 +782,7 @@ class ClientReport(Resource):
:returns: The *JSON* described above.
"""
server = server or self.parser.parse_args()['serverName']
j = []
json = []
if not name:
err = [[1, 'No client defined']]
self.abort(400, err)
......@@ -793,31 +793,74 @@ class ClientReport(Resource):
self.abort(403, 'You don\'t have rights to view this client report')
if backup:
try:
j = bui.client.get_backup_logs(backup, name, agent=server)
except BUIserverException as e:
self.abort(500, str(e))
json = bui.client.get_backup_logs(backup, name, agent=server)
except BUIserverException as exp:
self.abort(500, str(exp))
else:
try:
cl = bui.client.get_client(name, agent=server)
except BUIserverException as e:
self.abort(500, str(e))
client = bui.client.get_client(name, agent=server)
except BUIserverException as exp:
self.abort(500, str(exp))
err = []
for c in cl:
for back in client:
try:
j.append(
json.append(
bui.client.get_backup_logs(
c['number'],
back['number'],
name,
agent=server
)
)
except BUIserverException as e:
temp = [NOTIF_ERROR, str(e)]
except BUIserverException as exp:
temp = [NOTIF_ERROR, str(exp)]
if temp not in err:
err.append(temp)
if err:
self.abort(500, err)
return j
return json
@api.disabled_on_demo()
@ns.marshal_with(report_fields, code=202, description='Success')
@ns.expect(parser)
@ns.doc(
responses={
'400': 'Missing arguments',
'403': 'Insufficient permissions',
'500': 'Internal failure',
},
)
def delete(self, name, backup, server=None):
"""Deletes a given backup from the server
**DELETE** method provided by the webservice.
The access is filtered by the :mod:`burpui.misc.acl` module so that you
can only delete backups you have access to.
:param server: Which server to collect data from when in multi-agent
mode
:type server: str
:param name: The client we are working on
:type name: str
:param backup: The backup we are working on
:type backup: int
"""
server = server or self.parser.parse_args()['serverName']
if not name:
err = [[1, 'No client defined']]
self.abort(400, err)
if hasattr(current_user, 'acl') and \
not current_user.acl.is_admin() and \
not current_user.acl.is_client_allowed(name, server):
self.abort(403, 'You don\'t have rights on this client')
msg = bui.client.delete_backup(name, backup, server)
if msg:
self.abort(500, msg)
return 202, ''
@ns.route('/stats/<name>',
......@@ -909,7 +952,7 @@ class ClientStats(Resource):
not current_user.acl.is_admin() and \
not current_user.acl.is_client_allowed(name, server):
self.abort(403, 'Sorry, you cannot access this client')
j = bui.client.get_client(name, agent=server)
except BUIserverException as e:
self.abort(500, str(e))
return j
json = bui.client.get_client(name, agent=server)
except BUIserverException as exp:
self.abort(500, str(exp))
return json
......@@ -24,11 +24,6 @@ class ACLloader(BUIaclLoader):
]
self.moderators = []
self.grants = {}
self.clients = {}
self.servers = {}
self.read_only = {}
self.read_write = {}
self.raw_servers = {}
self.standalone = self.app.standalone
self.extended = False
self.legacy = False
......@@ -43,6 +38,12 @@ class ACLloader(BUIaclLoader):
if not self.conf.changed(self.conf_id):
return False
self.admins = [
'admin'
]
self.moderators = []
self.grants = {}
adms = []
mods = []
......
......@@ -865,6 +865,12 @@ class Burp(BUIbackend):
res.reverse()
return res
def is_backup_deletable(self, name=None, backup=None, agent=None):
"""Check if a given backup is deletable"""
# This feature won't be available in the burp 1 backend so we always
# return False
return False
def get_tree(self, name=None, backup=None, root=None, level=-1, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.get_tree`"""
res = []
......@@ -940,6 +946,27 @@ class Burp(BUIbackend):
"""See :func:`burpui.misc.backend.interface.BUIbackend.server_backup`"""
return self.parser.server_initiated_backup(client)
def delete_backup(self, name=None, backup=None, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.delete_backup`"""
if self._vers == 1:
return 'Sorry, this feature is not available'
if not name or not backup:
return 'At least one argument is missing'
if not self.burpbin:
return 'Missing \'burp\' binary'
if not self.is_backup_deletable(name, backup):
return 'Sorry, this backup is not deletable'
cmd = [self.burpbin, '-C', quote(name), '-a', 'delete', '-b', quote(str(backup)), '-c', self.burpconfcli]
self.logger.debug(cmd)
proc = subprocess.Popen(cmd, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
out, _ = proc.communicate()
status = proc.wait()
self.logger.debug(out)
self.logger.debug('command returned: %d', status)
if status != 0:
return 'The command failed with status {}: {}'.format(status, out)
return None
def restore_files(self, name=None, backup=None, files=None, strip=None, archive='zip', password=None, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.restore_files`"""
if not name or not backup or not files:
......
......@@ -952,6 +952,19 @@ class Burp(Burp1):
ret.reverse()
return ret
def is_backup_deletable(self, name=None, backup=None, agent=None):
"""Check if a given backup is deletable"""
if not name or not backup:
return False
query = self.status('c:{0}:b:{1}\n'.format(name, backup))
if not query:
return False
try:
flags = query['clients'][0]['backups'][0]['flags']
return 'deletable' in flags
except KeyError:
return False
def get_tree(self, name=None, backup=None, root=None, level=-1, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.get_tree`"""
ret = []
......
......@@ -423,6 +423,25 @@ class BUIbackend(with_metaclass(ABCMeta, object)):
"""
raise NotImplementedError("Sorry, the current Backend does not implement this method!") # pragma: no cover
@abstractmethod
def delete_backup(self, name=None, backup=None, agent=None):
"""The :func:`burpui.misc.backend.interface.BUIbackend.delete_backup`
function performs a backup deletion and returns an error message if
the command failed.
:param name: Client name
:type name: str
:param backup: Backup number
:type backup: int
:param agent: What server to ask (only in multi-agent mode)
:type agent: str
:returns: An error message if the command failed
"""
raise NotImplementedError("Sorry, the current Backend does not implement this method!") # pragma: no cover
@abstractmethod
def restore_files(self, name=None, backup=None, files=None, strip=None, archive='zip', password=None, agent=None):
"""The :func:`burpui.misc.backend.interface.BUIbackend.restore_files`
......
......@@ -24,7 +24,8 @@
<a class="toggle-vis" data-column="2" href="#">{{ _('Bytes received') }}</a>&nbsp;-
<a class="toggle-vis" data-column="3" href="#">{{ _('Estimated size') }}</a>&nbsp;-
<a class="toggle-vis" data-column="4" href="#">{{ _('Deletable') }}</a>&nbsp;-
<a class="toggle-vis" data-column="5" href="#">{{ _('Status') }}</a>
<a class="toggle-vis" data-column="5" href="#">{{ _('Status') }}</a>&nbsp;-
<a class="toggle-vis" data-column="5" href="#">{{ _('Delete') }}</a>
</p>
<div class="table-responsive">
<table class="table table-striped table-hover nowrap" id="table-client" width="100%">
......@@ -36,6 +37,7 @@
<th class="desktop">{{ _('Estimated size') }}</th>
<th class="desktop">{{ _('Deletable') }}</th>
<th class="desktop">{{ _('Status') }}</th>
<th class="desktop">{{ _('Delete') }}</th>
</tr>
</thead>
<tbody>
......
......@@ -404,12 +404,20 @@ $(function() {
/***
* add a listener to the '.clickable' element dynamically added in the document (see _client and _clients function)
*/
$( document ).on('click', '.clickable', function() {
$( document ).on('click', '.clickable', function(e) {
var that = e.target;
if ($(that).hasClass('no-link')) {
return;
}
if (!$(this).closest('table').hasClass('collapsed')) {
window.location = $(this).find('a').attr('href');
}
});
$( document ).on('click', 'td.child', function() {
$( document ).on('click', 'td.child', function(e) {
var that = e.target
if ($(that).hasClass('no-link')) {
return;
}
$before = $(this).parent().prev();
if ($before.hasClass('clickable')) {
window.location = $before.find('a').attr('href');
......
......@@ -96,6 +96,16 @@ var _client_table = $('#table-client').dataTable( {
render: function ( data, type, row ) {
return '<span class="glyphicon glyphicon-'+(data.encrypted?'lock':'globe')+'"></span>&nbsp;'+(data.encrypted?"{{ _('Encrypted backup') }}":"{{ _('Unencrypted backup') }}");
}
},
{
data: null,
render: function ( data, type, row ) {
var disable = '';
if (!data.deletable) {
disable = 'disabled="disabled"';
}
return '<button class="btn btn-danger btn-xs btn-delete-backup no-link" data-backup="' + data.number + '" ' + disable + '><i class="fa fa-trash" aria-hidden="true"></i>&nbsp;{{ _("Delete") }}</button>';
}
}
]
});
......@@ -135,7 +145,7 @@ var _client = function() {
{{ macros.page_length('#table-client') }}
$(document).ready(function() {
$( document ).ready(function() {
$('a.toggle-vis').on('click', function(e) {
e.preventDefault();
......@@ -199,3 +209,18 @@ $(document).ready(function() {
_client_table.on('draw.dt', function() {
$('[data-toggle="tooltip"]').tooltip();
});
/* this one is outside because the buttons are dynamically added after the
* document gets loaded
*/
$( document ).on('click', '.btn-delete-backup', function(e) {
$.ajax({
url: '{{ url_for("api.client_report", name=cname, server=server) }}/' + $(this).attr('data-backup'),
headers: { 'X-From-UI': true },
type: 'DELETE'
}).done(function(data) {
notif(NOTIF_SUCCESS, '{{ _("Delete task launched") }}');
/* refresh backups list now */
_client();
}).fail(myFail);
});
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment