Commit 2292ef82 authored by Ziirish's avatar Ziirish

add: new static templates (fix #280)

parent 1a8fcd5d
Pipeline #1699 passed with stages
in 10 minutes and 43 seconds
......@@ -13,6 +13,7 @@ Current
- Add: new `listen` and `listen_status` options in burp-2.2.10 `#279 <https://git.ziirish.me/ziirish/burp-ui/issues/279>`_
- Add: new `order` keyword in ACL definitions in order to decide whether `rw` should be evaluated first or not `#305 <https://git.ziirish.me/ziirish/burp-ui/issues/305>`__
- Add: new `exclude` keyword in ACL definitions in order to exclude some clients from the rules `#305 <https://git.ziirish.me/ziirish/burp-ui/issues/305>`__
- Add: new *static templates* that allow you to create *onetime* (variables) templates `#280 <https://git.ziirish.me/ziirish/burp-ui/issues/280>`_
- Add: allow to hide selected clients/servers `#282 <https://git.ziirish.me/ziirish/burp-ui/issues/282>`_
- Add: allow to delete clients data upon removal `#232 <https://git.ziirish.me/ziirish/burp-ui/issues/232>`_
- Add: allow to create clients from templates in one call `#266 <https://git.ziirish.me/ziirish/burp-ui/issues/266>`_
......
This diff is collapsed.
......@@ -1013,7 +1013,8 @@ class Burp(BUIbackend):
return []
return self.parser.read_server_conf(conf)
def store_conf_cli(self, data, client=None, conf=None, template=False, agent=None):
def store_conf_cli(self, data, client=None, conf=None, template=False,
statictemplate=False, content='', agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.store_conf_cli`"""
if not self.parser:
return []
......@@ -1021,7 +1022,7 @@ class Burp(BUIbackend):
conf = unquote(conf)
except:
pass
return self.parser.store_client_conf(data, client, conf, template)
return self.parser.store_client_conf(data, client, conf, template, statictemplate, content)
def store_conf_srv(self, data, conf=None, agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.store_conf_srv`"""
......
......@@ -844,7 +844,8 @@ class BUIbackend(object, metaclass=ABCMeta):
raise NotImplementedError("Sorry, the current Backend does not implement this method!") # pragma: no cover
@abstractmethod
def store_conf_cli(self, data, client=None, conf=None, template=False, agent=None):
def store_conf_cli(self, data, client=None, conf=None, template=False,
statictemplate=False, content='', agent=None):
"""The :func:`burpui.misc.backend.interface.BUIbackend.store_conf_cli`
function works the same way as the
:func:`burpui.misc.backend.interface.BUIbackend.store_conf_srv` function
......
......@@ -523,7 +523,8 @@ class NClient(BUIbackend):
"""
@implement
def store_conf_cli(self, data, client=None, conf=None, template=False, agent=None):
def store_conf_cli(self, data, client=None, conf=None, template=False,
statictemplate=False, content='', agent=None):
"""See :func:`burpui.misc.backend.interface.BUIbackend.store_conf_cli`"""
# serialize data as it is a nested dict
import hmac
......@@ -539,7 +540,14 @@ class NClient(BUIbackend):
data = ImmutableMultiDict(data.to_dict(False))
key = '{}{}'.format(self.password, 'store_conf_cli')
key = to_bytes(key)
pickles = to_unicode(b64encode(pickle.dumps({'data': data, 'conf': conf, 'client': client, 'template': template}, 2)))
pickles = to_unicode(
b64encode(
pickle.dumps(
{'data': data, 'conf': conf, 'client': client, 'template': template,
'statictemplate': statictemplate, 'content': content}, 2
)
)
)
bytes_pickles = to_bytes(pickles)
digest = to_unicode(hmac.new(key, bytes_pickles, hashlib.sha1).hexdigest())
data = {'func': 'store_conf_cli', 'args': pickles, 'pickled': True, 'digest': digest}
......
This diff is collapsed.
......@@ -133,7 +133,8 @@ class BUIparser(object, metaclass=ABCMeta):
) # pragma: no cover
@abstractmethod
def store_client_conf(self, data, client=None, conf=None, template=False):
def store_client_conf(self, data, client=None, conf=None, template=False,
statictemplate=False, content=''):
""":func:`burpui.misc.parser.interface.BUIparser.store_client_conf` is
used by :func:`burpui.misc.backend.BUIbackend.store_conf_cli`.
......@@ -149,6 +150,12 @@ class BUIparser(object, metaclass=ABCMeta):
:param template: Is this file a template
:type template: bool
:param statictemplate: Whether we remove a static template
:type statictemplate: bool
:param content: What default content to put in the file
:type content: str
"""
raise NotImplementedError(
"Sorry, the current Parser does not implement this method!"
......@@ -156,7 +163,7 @@ class BUIparser(object, metaclass=ABCMeta):
@abstractmethod
def store_conf(self, data, conf=None, client=None, mode='srv',
insecure=False, template=False):
insecure=False, template=False, statictemplate=False, content=''):
""":func:`burpui.misc.parser.interface.BUIparser.store_conf` is used to
store the configuration from the web-ui into the actual configuration
files.
......@@ -181,6 +188,12 @@ class BUIparser(object, metaclass=ABCMeta):
:param template: Is it a template
:type template: bool
:param statictemplate: Whether we remove a static template
:type statictemplate: bool
:param content: What default content to put in the file
:type content: str
:returns: A list of notifications to return to the UI (success or
failure)
......@@ -248,6 +261,17 @@ class BUIparser(object, metaclass=ABCMeta):
"Sorry, the current Parser does not implement this method!"
) # pragma: no cover
@abstractmethod
def list_static_templates(self):
""":func:`burpui.misc.parser.interface.BUIparser.list_static_templates` is used
to retrieve a list of static templates with their absolute paths.
:returns: A list of templates
"""
raise NotImplementedError(
"Sorry, the current Parser does not implement this method!"
) # pragma: no cover
@abstractmethod
def is_client_revoked(self, client=None):
""":func:`burpui.misc.parser.interface.BUIparser.is_client_revoked` is
......@@ -264,7 +288,7 @@ class BUIparser(object, metaclass=ABCMeta):
@abstractmethod
def remove_client(self, client=None, keepconf=False, delcert=False, revoke=False,
template=False, delete=False):
template=False, statictemplate=False, delete=False):
""":func:`burpui.misc.parser.interface.BUIparser.remove_client` is used
to delete a client from burp's configuration.
......@@ -283,6 +307,9 @@ class BUIparser(object, metaclass=ABCMeta):
:param template: Whether we remove a template
:type template: bool
:param statictemplate: Whether we remove a static template
:type statictemplate: bool
:param delete: Whether to remove data as well
:type delete: bool
......@@ -295,8 +322,8 @@ class BUIparser(object, metaclass=ABCMeta):
) # pragma: no cover
@abstractmethod
def rename_client(self, client=None, newname=None, template=False, keepcert=False,
keepdata=False):
def rename_client(self, client=None, newname=None, template=False,
statictemplate=False, keepcert=False, keepdata=False):
""":func:`burpui.misc.parser.interface.BUIParser.rename_client` is used to
rename a client.
......@@ -309,6 +336,9 @@ class BUIparser(object, metaclass=ABCMeta):
:param template: Whether we remove a template
:type template: bool
:param statictemplate: Whether we remove a static template
:type statictemplate: bool
:param keepcert: Whether to keep using the same certificate or not
:type keepcert: bool
......@@ -324,7 +354,7 @@ class BUIparser(object, metaclass=ABCMeta):
) # pragma: no cover
@abstractmethod
def read_client_conf(self, client=None, conf=None, template=False):
def read_client_conf(self, client=None, conf=None, template=False, statictemplate=False):
""":func:`burpui.misc.parser.interface.BUIparser.read_client_conf` is
called by :func:`burpui.misc.backend.interface.BUIbackend.read_conf_cli`
in order to parse the burp-clients configuration files.
......
......@@ -254,11 +254,13 @@ def cli_settings(server=None, client=None, conf=None):
client = client or request.args.get('client')
server = server or request.args.get('serverName')
template = request.args.get('template') or False
statictemplate = request.args.get('statictemplate') or False
return render_template(
'settings.html',
settings=True,
client_mode=True,
template=template,
statictemplate=statictemplate,
client=client,
server=server,
conf=conf,
......
......@@ -187,6 +187,8 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
{% if client -%}
{% if template -%}
$http.get('{{ url_for("api.client_settings", client=client, conf=conf, template=True, server=server) }}', { headers: { 'X-From-UI': true } })
{% elif statictemplate -%}
$http.get('{{ url_for("api.client_settings", client=client, conf=conf, statictemplate=True, server=server) }}', { headers: { 'X-From-UI': true } })
{% else -%}
$http.get('{{ url_for("api.client_settings", client=client, conf=conf, server=server) }}', { headers: { 'X-From-UI': true } })
{% endif -%}
......@@ -603,6 +605,25 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
}
);
};
$scope.getStaticTemplatesList = function() {
api = '{{ url_for("api.static_templates_list", server=server) }}';
$http.get(
api,
{
headers: { 'X-From-UI': true },
}
).then(
function(response) {
var data = response.data;
$scope.raw.static_templates = data.result;
$scope.raw.static_templates.splice(0, 0, {'name': '{{ _("None") }}'});
$scope.all.static_templates = {};
_(data.result).forEach(function(r) {
$scope.all.static_templates[r.name] = {'value': r.value, 'variables': r.variables};
});
}
);
};
$scope.deleteFile = function() {
/* UX tweak: disable the submit button + change text */
submit = $('#btn-remove-file');
......@@ -650,6 +671,8 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
type: 'PUT',
{% if template -%}
data: {template: true, newname: $('#newname').val() }
{% elif statictemplate -%}
data: {statictemplate: true, newname: $('#newname').val() }
{% else -%}
data: { newname: $('#newname').val(), keepcert: $('#keepcert').is(':checked'), keepdata: $('#keepdata').is(':checked') }
{% endif -%}
......@@ -688,6 +711,8 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
type: 'DELETE',
{% if template -%}
data: { template: true }
{% elif statictemplate -%}
data: {statictemplate: true }
{% else -%}
data: { delcert: $('#delcert').is(':checked'), revoke: $('#revoke').is(':checked'), keepconf: $('#keepconf').is(':checked'), delete: $('#deldata').is(':checked') }
{% endif -%}
......@@ -739,11 +764,24 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
e.preventDefault();
var form = $(e.target);
var templates = form.find('input[name="templates"]');
submit = form.find('button[type="submit"]');
sav = submit.html();
var variables = form.find('input[name="variables"]');
var submit = form.find('button[type="submit"]');
var disabled = [];
var sav = submit.html();
if ($scope.newclient.templates) {
templates.val($scope.newclient.templates.join(','));
}
if ($scope.newclient.statictemplate && $scope.newclient.statictemplate != 'None') {
temp = {};
_($('.static-variables').find('input')).forEach(function(raw) {
var input = $(raw);
console.log(input);
temp[input.attr('name')] = input.val();
input.attr('disabled', true);
disabled.push(input);
});
variables.val(JSON.stringify(temp));
}
submit.html('<i class="fa fa-fw fa-spinner fa-pulse" aria-hidden="true"></i>&nbsp;{{ _("Creating...") }}');
submit.attr('disabled', true);
$.ajax({
......@@ -766,6 +804,9 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
.always(function() {
submit.attr('disabled', false);
submit.html(sav);
_(disabled).forEach(function(input) {
input.attr('disabled', false);
});
});
};
$scope.createTemplate = function(e) {
......@@ -798,6 +839,36 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
submit.html(sav);
});
};
$scope.createStaticTemplate = function(e) {
/* we disable the 'real' form submission */
e.preventDefault();
var form = $(e.target);
submit = form.find('button[type="submit"]');
sav = submit.html();
submit.html('<i class="fa fa-fw fa-spinner fa-pulse" aria-hidden="true"></i>&nbsp;{{ _("Creating...") }}');
submit.attr('disabled', true);
$.ajax({
url: form.attr('action'),
type: 'PUT',
data: form.serialize()
})
.fail(buiFail)
.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] == NOTIF_SUCCESS) {
$scope.getStaticTemplatesList();
notif(data.notif[1][0], data.notif[1][1], 20000);
}
}
})
.always(function() {
submit.attr('disabled', false);
submit.html(sav);
});
};
$scope.isNumber = function(key) {
return $scope.advanced && $scope.advanced[key] === 'integer';
};
......@@ -823,10 +894,12 @@ app.controller('ConfigCtrl', ['$scope', '$http', '$timeout', '$scrollspy', 'DTOp
$scope.loadConfig();
$scope.getClientsList();
$scope.getTemplatesList();
$scope.getStaticTemplatesList();
}]);
{{ macros.page_length('#table-list-clients') }}
{{ macros.page_length('#table-list-templates') }}
{{ macros.page_length('#table-list-static-templates') }}
$(document).ready(function () {
$('#config-nav a').click(function (e) {
......
......@@ -18,6 +18,8 @@
{% else -%}
{% if template -%}
<li class="active">{{ _('Template %(name)s on %(server)s', name=client, server=server) }}</li>
{% elif statictemplate -%}
<li class="active">{{ _('Static template %(name)s on %(server)s', name=client, server=server) }}</li>
{% else -%}
<li class="active">{{ _('%(client)s on %(server)s', client=client, server=server) }}</li>
{% endif -%}
......@@ -40,6 +42,8 @@
{% else -%}
{% if template -%}
<li class="active">{{ _('Template %(name)s', name=client) }}</li>
{% elif statictemplate -%}
<li class="active">{{ _('Static template %(name)s', name=client) }}</li>
{% else -%}
<li class="active">{{ client }}</li>
{% endif -%}
......@@ -64,6 +68,7 @@
{% endif -%}
<li {% if is_moderator and not is_admin %}class="active"{% endif %}><a href="#clients" data-toggle="tab" aria-expanded="false">{{ _('Clients') }}</a></li>
<li><a href="#list-templates" data-toggle="tab" aria-expanded="false">{{ _('Templates') }}</a></li>
<li><a href="#list-static-templates" data-toggle="tab" aria-expanded="false">{{ _('Static templates') }}</a></li>
</ul>
<div id="config-tab-content" class="tab-content">
{% if (not is_moderator and is_admin) or client_mode -%}
......@@ -80,6 +85,8 @@
{% if client -%}
{% if template -%}
<form class="form-horizontal" action="{{ url_for('api.client_settings', client=client, conf=conf, template=template, server=server) }}" method="POST" ng-submit="submit($event)" name="setSettings" onbeforeunload>
{% elif statictemplate -%}
<form class="form-horizontal" action="{{ url_for('api.client_settings', client=client, conf=conf, statictemplate=statictemplate, server=server) }}" method="POST" ng-submit="submit($event)" name="setSettings" onbeforeunload>
{% else -%}
<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>
{% endif -%}
......@@ -486,7 +493,32 @@
</ui-select-choices>
</ui-select>
</div>
<div class="form-group">
<ui-select ng-model="newclient.statictemplate" ng-disabled="!raw.static_templates" theme="bootstrap">
<ui-select-match placeholder="{{ _('Select a static template') }}">
{% raw %}
{{ $select.selected.name }}
{% endraw %}
</ui-select-match>
<ui-select-choices repeat="value.name as value in raw.static_templates | filter: $select.search">
<div ng-bind-html="value.name | highlight: $select.search"></div>
</ui-select-choices>
</ui-select>
</div>
<div class="static-variables" ng-if="newclient.statictemplate && newclient.statictemplate != 'None'">
<div class="form-group row" ng-repeat="val in all.static_templates[newclient.statictemplate]['variables'] track by $index">
{% raw -%}
<label for="static-variables_{{ val }}" class="col-lg-2 control-label">{{ val }}</label>
<div class="col-lg-10">
<input type="text" class="form-control" id="static-variables_{{ val }}" name="{{ val }}"
{% endraw -%}
placeholder="{{ _('Enter value') }}">
</div>
</div>
</div>
<input type="hidden" name="templates">
<input type="hidden" name="variables">
<input type="hidden" name="statictemplate" ng-value="newclient.statictemplate">
</fieldset>
</form>
</div>
......@@ -525,6 +557,40 @@
</form>
</div>
</div>
<div class="tab-pane fade" id="list-static-templates">
<div style="padding-top: 80px; margin-top: -45px;"></div>
<div id="table-static-templates" class="table-responsive row">
<table class="table table-striped table-hover nowrap" width="100%" datatable="ng" dt-options="dtOptions" dt-column-defs="dtColumnDefs" id="table-list-static-templates">
<thead>
<tr>
<th>{{ _('Name') }}</th><th>{{ _('Path') }}</th><th></th>
</tr>
</thead>
<tbody>
<tr ng-repeat="template in raw.static_templates" ng-if="template.name != 'None'">
{% raw -%}
<td>{{ template.name }}</td>
<td>{{ template.value }}</td>
{% endraw -%}
<td><a href="{{ url_for("view.cli_settings", server=server) }}?client={{ '{{' }} template.name {{ '}}' }}&statictemplate=true" class="btn btn-info btn-xs no-link pull-right" title="{{ _('edit') }}"><i class="fa fa-pencil" aria-hidden="true"></i></a></td>
</tr>
</tbody>
</table>
</div>
<div class="row well">
<form action="{{ url_for('api.new_static_template', server=server) }}" method="POST" ng-submit="createStaticTemplate($event)">
<fieldset>
<Legend>{{ _('Create new static template') }}</Legend>
<div class="input-group">
<input class="form-control" type="text" name="newstatictemplate" id="newstatictemplate" placeholder="{{ _('Create new static template') }}">
<span class="input-group-btn">
<button class="btn btn-success" type="submit"><i class="fa fa-plus" aria-hidden="true"></i></button>
</span>
</div>
</fieldset>
</form>
</div>
</div>
</div>
</div>
{% endblock %}
......@@ -68,6 +68,14 @@ v0.7.0
from any rule with the ``exclude`` keyword. See the
`BASIC ACL <advanced_usage.html#basic-acl>`__ documentation for details.
- **New** - You can now create *static templates* which support `jinja2
variables <https://jinja.palletsprojects.com/en/2.10.x/templates/#variables>`_
format. These templates are applied only *once* at the creation of a new
client if you choose to use them. Also note there are two default variables:
``{{client}}`` and ``{{agent}}`` injected while rendering them which contain
respectively the name of the *client* being created and the name of the
*agent* you are working on.
v0.6.0
------
......
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