Commit 26ebab65 authored by Ziirish's avatar Ziirish

allow to rename client/templates

parent 7464f9d7
Pipeline #1679 failed with stages
in 8 minutes and 18 seconds
......@@ -17,7 +17,8 @@ from ..decorators import browser_cache
from ..ext.cache import cache
from ..ext.i18n import LANGUAGES
from flask import flash, url_for, current_app, session
from flask import flash, get_flashed_messages, url_for, current_app, session
from flask.ctx import _request_ctx_stack
from flask_login import current_user
import random
......@@ -329,6 +330,10 @@ class Alert(Resource):
return 'danger'
return new
# retrieve last flashed messages so we don't loose anything
for level, message in get_flashed_messages(with_categories=True):
flash(message, level)
args = self.parser.parse_args()
message = args['message']
level = translate(args['level'])
......
......@@ -475,7 +475,7 @@ class NewClientSettings(Resource):
'/<server>/config/<client>',
'/<server>/config/<client>/<path:conf>',
endpoint='client_settings',
methods=['GET', 'POST', 'DELETE'])
methods=['GET', 'POST', 'PUT', 'DELETE'])
@ns.doc(
params={
'server': 'Which server to collect data from when in multi-agent mode',
......@@ -490,6 +490,11 @@ class ClientSettings(Resource):
parser_delete.add_argument('keepconf', type=inputs.boolean, help='Whether to keep the conf or not', default=False, nullable=True)
parser_delete.add_argument('template', type=inputs.boolean, help='Whether we work on a template or not', default=False, nullable=True)
parser_delete.add_argument('delete', type=inputs.boolean, help='Whether we should remove the data as well or not', default=False, nullable=True)
parser_put = ns.parser()
parser_put.add_argument('newname', help='New name of the client/template')
parser_put.add_argument('template', type=inputs.boolean, help='Whether we work on a template or not', default=False, nullable=True)
parser_put.add_argument('keepcert', type=inputs.boolean, help='Whether to keep the same certificate or not', default=False, nullable=True)
parser_put.add_argument('keepdata', type=inputs.boolean, help='Whether to keep the data or not', default=False, nullable=True)
parser_post = ns.parser()
parser_post.add_argument('template', type=inputs.boolean, help='Whether we work on a template or not', default=False, nullable=True)
parser_get = ns.parser()
......@@ -636,6 +641,54 @@ class ClientSettings(Resource):
f'{keepconf}, delete data: {delete}, is template: {template}', server=server)
return parser.remove_client(client, keepconf, delcert, revoke, template, delete), 200
@api.disabled_on_demo()
@api.acl_admin_or_moderator_required(message=_('Sorry, you don\'t have rights to access the setting panel'))
@ns.expect(parser_put)
@ns.doc(
responses={
200: 'Success',
403: 'Insufficient permissions',
409: 'Conflict',
500: 'Internal failure',
}
)
def put(self, server=None, client=None, conf=None):
"""Rename a given client"""
if not current_user.is_anonymous and \
current_user.acl.is_moderator() and \
not current_user.acl.is_client_rw(client, server):
self.abort(403, 'You don\'t have rights on this server')
if bui.client.is_backup_running(client, server):
self.abort(409, 'There is currently a backup running for this client hence '
'we cannot delete it for now. Please try again later')
args = self.parser_put.parse_args()
newname = args.get('newname', None)
keepcert = args.get('keepcert', False)
keepdata = args.get('keepdata', False)
template = args.get('template', False)
# clear the cache when we remove a client
cache.clear()
# clear client-side cache through the _extra META variable
try:
_extra = session.get('_extra', g.now)
_extra = int(_extra)
except ValueError:
_extra = 0
session['_extra'] = '{}'.format(_extra + 1)
if bui.config['WITH_CELERY']:
from ..tasks import force_scheduling_now
force_scheduling_now()
parser = bui.client.get_parser(agent=server)
bui.audit.logger.info(
f'renaming client configuration {client} to {newname}, '
f'keep data: {keepdata}, keep certificate: {keepcert}, '
f'is template: {template}', server=server)
return parser.rename_client(client, newname, template, keepcert, keepdata), 200
@ns.route('/path-expander',
'/<server>/path-expander',
......
......@@ -197,7 +197,10 @@ class Parser(Doc):
return False
def _get_client(self, name, path):
"""Return client conf and refresh it if necessary"""
"""Return client conf and refresh it if necessary
:rtype: Config
"""
if self._clientconfdir_changed() and name not in self._clients_conf:
self._clients_conf.clear()
self._load_conf_clients()
......@@ -208,7 +211,10 @@ class Parser(Doc):
return self._clients_conf[name]
def _get_template(self, name, path=None):
"""Return template conf and refresh it if necessary"""
"""Return template conf and refresh it if necessary
:rtype: Config
"""
if self._clientconfdir_changed() and name not in self._templates_conf:
self._templates_conf.clear()
self._load_conf_templates()
......@@ -219,7 +225,10 @@ class Parser(Doc):
return self._templates_conf[name]
def _get_config(self, path, mode='cli'):
"""Return conf by it's path"""
"""Return conf by it's path
:rtype: Config
"""
if path in self._configs:
ret = self._configs[path]
else:
......@@ -403,6 +412,79 @@ class Parser(Doc):
return res
def rename_client(self, client=None, newname=None, template=False, keepcert=False,
keepdata=False):
"""See :func:`burpui.misc.parser.interface.BUIparser.rename_client`"""
res = []
if not client:
return [[NOTIF_ERROR, "No client provided"]]
if not newname:
return [[NOTIF_ERROR, "No newname provided"]]
if template:
path = os.path.join(self.templates_path, client)
newpath = os.path.join(self.templates_path, newname)
else:
data = self._get_server_path(client)
conf = self.clients_conf.get(client)
newdata = os.path.join(conf.get('directory'), newname)
path = os.path.join(self.clientconfdir, client)
newpath = os.path.join(self.clientconfdir, newname)
try:
os.rename(path, newpath)
res.append([NOTIF_OK, "'{}' successfully renamed '{}'".format(client, newname)])
except OSError as exp:
res.append([NOTIF_ERROR, str(exp)])
if client in self._clients_conf and not template:
del self._clients_conf[client]
elif template and client in self._templates_conf:
del self._templates_conf[client]
if path in self.filescache:
del self.filescache[path]
self._refresh_cache()
if template:
return res
if keepdata:
if os.path.exists(newdata):
res.append([NOTIF_ERROR, "'{}' already exists".format(newdata)])
else:
try:
shutil.move(data, newdata)
except OSError as exp:
res.append([NOTIF_ERROR, str(exp)])
ca_dir = self.openssl_conf.values.get('CA_DIR')
path = os.path.join(ca_dir, client)
newpath = os.path.join(ca_dir, newname)
if keepcert:
config = self._get_client(newname, newpath) # type: Config
if 'ssl_peer_cn' not in config:
config['ssl_peer_cn'] = client
config.store()
try:
os.rename(f"{path}.csr", f"{newpath}.csr")
except OSError as exp:
res.append([NOTIF_WARN, str(exp)])
try:
os.rename(f"{path}.crt", f"{newpath}.crt")
except OSError as exp:
res.append([NOTIF_ERROR, str(exp)])
else:
try:
os.unlink('{}.csr'.format(path))
except OSError as exp:
res.append([NOTIF_WARN, str(exp)])
try:
os.unlink('{}.crt'.format(path))
except OSError as exp:
res.append([NOTIF_ERROR, str(exp)])
return res
def read_client_conf(self, client=None, conf=None, template=False):
"""
See :func:`burpui.misc.parser.interface.BUIparser.read_client_conf`
......
......@@ -294,6 +294,35 @@ class BUIparser(object, metaclass=ABCMeta):
"Sorry, the current Parser does not implement this method!"
) # pragma: no cover
@abstractmethod
def rename_client(self, client=None, newname=None, template=False, keepcert=False,
keepdata=False):
""":func:`burpui.misc.parser.interface.BUIParser.rename_client` is used to
rename a client.
:param client: The name of the client to rename
:type client: str
:param newname: The new name of the client
:type newname: str
:param template: Whether we remove a template
:type template: bool
:param keepcert: Whether to keep using the same certificate or not
:type keepcert: bool
:param keepdata: Whether to keep the already saved data or not
:type keepdata: bool
:returns: A list of notifications to return to the UI (success or
failure)
:rtype: list
"""
raise NotImplementedError(
"Sorry, the current Parser does not implement this method!"
) # pragma: no cover
@abstractmethod
def read_client_conf(self, client=None, conf=None, template=False):
""":func:`burpui.misc.parser.interface.BUIparser.read_client_conf` is
......
This diff is collapsed.
......@@ -63,6 +63,7 @@ function escapeDoubleQuotes(str) {
var notifAll = function(messages, forward) {
forward = (typeof forward === "undefined") ? false : forward;
jQuery.ajaxSetup({async:false});
if (messages instanceof Array && messages.length > 0) {
if (messages[0] instanceof Array) {
$.each(messages, function(i, n) {
......@@ -90,6 +91,7 @@ var notifAll = function(messages, forward) {
}
}
}
jQuery.ajaxSetup({async:true});
};
var notif = function(type, message, timeout) {
......
......@@ -264,6 +264,8 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
form.find('#'+value.name+'_reset_bui_CUSTOM_view-'+i).attr('disabled', true);
});
});
/* disable the newname fields */
form.find('#newname').attr('disabled', true);
$scope.invalid = {};
/* UX tweak: disable the submit button + change text */
submit = form.find('button[type="submit"]');
......@@ -304,6 +306,8 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
submit.html(sav);
submit.attr('disabled', false);
});
/* re-enable the newname field */
form.find('#newname').attr('disabled', false);
/* re-enable the checkboxes */
angular.forEach($scope.bools, function(value, key) {
form.find('#'+value.name+'_view').attr('disabled', false);
......@@ -625,6 +629,39 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
submit.attr('disabled', false);
});
};
$scope.renameClient = function() {
submit = $('#btn-rename-client');
parse_result = function(data) {
redirect = data[0][0] == NOTIF_SUCCESS;
notifAll(data, redirect);
if (redirect) {
$timeout(function() {
document.location = '{{ url_for("view.cli_settings", server=server) }}?client=' + $('#newname').val();
}, 1000);
}
};
sav = submit.html();
submit.html('<i class="fa fa-fw fa-spinner fa-pulse" aria-hidden="true"></i>&nbsp;{{ _("Renaming...") }}');
submit.attr('disabled', true);
api = '{{ url_for("api.client_settings", client=client, server=server) }}';
$.ajax({
url: api,
type: 'PUT',
{% if template -%}
data: {template: true, newname: $('#newname').val() }
{% else -%}
data: { newname: $('#newname').val(), keepcert: $('#keepcert').is(':checked'), keepdata: $('#keepdata').is(':checked') }
{% endif -%}
})
.fail(buiFail)
.done(function(data) {
parse_result(data);
})
.always(function() {
submit.attr('disabled', false);
submit.html(sav);
});
};
$scope.deleteClient = function() {
/* UX tweak: disable the submit button + change text */
submit = $('#btn-remove-client');
......
......@@ -407,6 +407,17 @@
</ul>
{% endif -%}
</div>
<div class="btn-group dropup text-right">
<button type="button" class="btn btn-warning" id="btn-rename-client" ng-click="renameClient()"><i class="fa fa-fw fa-share" aria-hidden="true"></i>&nbsp;{{ _("Rename '%(client)s'", client=client) }}</button>
<button class="btn btn-warning dropdown-toggle" data-toggle="dropdown"><span class="caret"></span></button>
<ul class="dropdown-menu browse">
<li><label for="newname">{{ _('New name:') }}&nbsp;</label><input type="input" id="newname" name="newname"></li>
{% if not template -%}
<li><label for="keepcert">{{ _('Keep associated certificate:') }}&nbsp;</label><input type="checkbox" id="keepcert" name="keepcert"></li>
<li><label for="keepdata">{{ _('Keep associated data:') }}&nbsp;</label><input type="checkbox" id="keepdata" name="keepdata"></li>
{% endif -%}
</ul>
</div>
{% elif conf -%}
<button type="button" class="btn btn-danger" id="btn-remove-file" ng-click="deleteFile()"><i class="fa fa-fw fa-trash" aria-hidden="true"></i>{{ _("Remove file") }}</button>
{% endif -%}
......
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