mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-15 14:16:08 -06:00
add: new static templates (fix #280)
This commit is contained in:
parent
1a8fcd5da9
commit
2292ef826f
11 changed files with 477 additions and 36 deletions
|
|
@ -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>`_
|
||||
|
|
|
|||
|
|
@ -7,6 +7,8 @@
|
|||
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
|
||||
|
||||
"""
|
||||
import json
|
||||
|
||||
from . import api
|
||||
from ..engines.server import BUIServer # noqa
|
||||
from ..ext.cache import cache
|
||||
|
|
@ -15,11 +17,14 @@ from .._compat import unquote
|
|||
from ..utils import NOTIF_INFO
|
||||
|
||||
from flask_babel import gettext as _, refresh
|
||||
from flask import jsonify, request, url_for, current_app, g, session
|
||||
from flask import jsonify, request, url_for, current_app, g, session, render_template_string
|
||||
from flask_login import current_user
|
||||
from flask_restplus import inputs
|
||||
from jinja2 import Environment, meta
|
||||
from ..datastructures import ImmutableMultiDict, MultiDict
|
||||
|
||||
TEMPLATE_EXCLUDES = ['client', 'agent']
|
||||
|
||||
bui = current_app # type: BUIServer
|
||||
ns = api.namespace('settings', 'Settings methods')
|
||||
|
||||
|
|
@ -323,6 +328,89 @@ class ClientsList(Resource):
|
|||
return jsonify(result=res)
|
||||
|
||||
|
||||
@ns.route('/static-templates',
|
||||
'/<server>/static-templates',
|
||||
endpoint='static_templates_list')
|
||||
@ns.doc(
|
||||
params={
|
||||
'server': 'Which server to collect data from when in multi-agent mode',
|
||||
},
|
||||
)
|
||||
class StaticTemplatesList(Resource):
|
||||
|
||||
@api.acl_admin_or_moderator_required(message='Sorry, you don\'t have rights to access the setting panel')
|
||||
@ns.doc(
|
||||
responses={
|
||||
200: 'Success',
|
||||
403: 'Insufficient permissions',
|
||||
500: 'Internal failure',
|
||||
}
|
||||
)
|
||||
def get(self, server=None):
|
||||
"""Returns a list of clients"""
|
||||
parser = bui.client.get_parser(agent=server)
|
||||
res = parser.list_static_templates()
|
||||
env = Environment()
|
||||
for obj in res:
|
||||
ast = env.parse(obj['content'])
|
||||
obj['variables'] = [x for x in meta.find_undeclared_variables(ast) if x not in TEMPLATE_EXCLUDES]
|
||||
return jsonify(result=res)
|
||||
|
||||
|
||||
@ns.route('/static-template',
|
||||
'/<server>/static-template',
|
||||
endpoint='new_static_template',
|
||||
methods=['PUT'])
|
||||
@ns.doc(
|
||||
params={
|
||||
'server': 'Which server to collect data from when in multi-agent mode',
|
||||
},
|
||||
)
|
||||
class NewStaticTemplateSettings(Resource):
|
||||
parser = ns.parser()
|
||||
parser.add_argument('newstatictemplate', required=True, help="No 'newstatictemplate' provided")
|
||||
|
||||
@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)
|
||||
@ns.doc(
|
||||
responses={
|
||||
200: 'Success',
|
||||
400: 'Missing parameter',
|
||||
403: 'Insufficient permissions',
|
||||
500: 'Internal failure',
|
||||
}
|
||||
)
|
||||
def put(self, server=None):
|
||||
"""Creates a new template"""
|
||||
if not current_user.is_anonymous and \
|
||||
current_user.acl.is_moderator() and \
|
||||
not current_user.acl.is_server_rw(server):
|
||||
self.abort(403, 'You don\'t have rights on this server')
|
||||
|
||||
newtemplate = self.parser.parse_args()['newstatictemplate']
|
||||
if not newtemplate:
|
||||
self.abort(400, 'No template name provided')
|
||||
parser = bui.client.get_parser(agent=server)
|
||||
templates = parser.list_static_templates()
|
||||
if any(tpl['name'] == newtemplate for tpl in templates):
|
||||
self.abort(409, "Static template '{}' already exists".format(newtemplate))
|
||||
# clientconfdir = bui.client.get_parser_attr('clientconfdir', server)
|
||||
# if not clientconfdir:
|
||||
# flash('Could not proceed, no \'clientconfdir\' find', 'warning')
|
||||
# return redirect(request.referrer)
|
||||
noti = bui.client.store_conf_cli(ImmutableMultiDict(), newtemplate, None, False, True, server)
|
||||
if server:
|
||||
url = url_for('view.cli_settings', server=server, client=newtemplate, statictemplate=True)
|
||||
else:
|
||||
url = url_for('view.cli_settings', client=newtemplate, statictemplate=True)
|
||||
noti.append([NOTIF_INFO, _('<a href="%(url)s">Click here</a> to edit \'%(template)s\' configuration', url=url, template=newtemplate)])
|
||||
# clear the cache when we add a new client
|
||||
cache.clear()
|
||||
bui.audit.logger.info(f'created new static template {newtemplate}', server=server)
|
||||
return {'notif': noti}, 201
|
||||
|
||||
|
||||
@ns.route('/templates',
|
||||
'/<server>/templates',
|
||||
endpoint='templates_list')
|
||||
|
|
@ -391,7 +479,7 @@ class NewTemplateSettings(Resource):
|
|||
# if not clientconfdir:
|
||||
# flash('Could not proceed, no \'clientconfdir\' find', 'warning')
|
||||
# return redirect(request.referrer)
|
||||
noti = bui.client.store_conf_cli(ImmutableMultiDict(), newtemplate, None, True, server)
|
||||
noti = bui.client.store_conf_cli(ImmutableMultiDict(), newtemplate, None, True, False, server)
|
||||
if server:
|
||||
url = url_for('view.cli_settings', server=server, client=newtemplate, template=True)
|
||||
else:
|
||||
|
|
@ -416,6 +504,8 @@ class NewClientSettings(Resource):
|
|||
parser = ns.parser()
|
||||
parser.add_argument('newclient', required=True, help="No 'newclient' provided")
|
||||
parser.add_argument('templates', help="Templates list", action='split')
|
||||
parser.add_argument('statictemplate', help="Static template")
|
||||
parser.add_argument('variables', help="Template variables")
|
||||
|
||||
@api.disabled_on_demo()
|
||||
@api.acl_admin_or_moderator_required(message='Sorry, you don\'t have rights to access the setting panel')
|
||||
|
|
@ -433,6 +523,10 @@ class NewClientSettings(Resource):
|
|||
args = self.parser.parse_args()
|
||||
newclient = args['newclient']
|
||||
templates = [x for x in args.get('templates', []) if x]
|
||||
statictemplate = args['statictemplate']
|
||||
variables = json.loads(args['variables']) if args['variables'] else {}
|
||||
variables['agent'] = server
|
||||
variables['client'] = newclient
|
||||
if not newclient:
|
||||
self.abort(400, 'No client name provided')
|
||||
|
||||
|
|
@ -451,12 +545,18 @@ class NewClientSettings(Resource):
|
|||
# flash('Could not proceed, no \'clientconfdir\' find', 'warning')
|
||||
# return redirect(request.referrer)
|
||||
data = MultiDict()
|
||||
content = ''
|
||||
if templates:
|
||||
real_templates = {x['name']: x['value'] for x in parser._list_templates()}
|
||||
if any(x not in real_templates for x in templates):
|
||||
self.abort(400, 'Wrong template')
|
||||
data.setlist('templates', [real_templates[x] for x in templates])
|
||||
noti = bui.client.store_conf_cli(ImmutableMultiDict(data), newclient, None, agent=server)
|
||||
if statictemplate:
|
||||
statics = parser._list_static_templates()
|
||||
for tpl in statics:
|
||||
if tpl['name'] == statictemplate:
|
||||
content = render_template_string(tpl['content'], **variables)
|
||||
noti = bui.client.store_conf_cli(ImmutableMultiDict(data), newclient, None, content=content, agent=server)
|
||||
if server:
|
||||
url = url_for('view.cli_settings', server=server, client=newclient)
|
||||
else:
|
||||
|
|
@ -498,16 +598,20 @@ class ClientSettings(Resource):
|
|||
parser_delete.add_argument('delcert', type=inputs.boolean, help='Whether to delete the certificate or not', default=False, nullable=True)
|
||||
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('statictemplate', type=inputs.boolean, help='Whether we work on a static 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('statictemplate', type=inputs.boolean, help='Whether we work on a static 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_post.add_argument('statictemplate', type=inputs.boolean, help='Whether we work on a static template or not', default=False, nullable=True)
|
||||
parser_get = ns.parser()
|
||||
parser_get.add_argument('template', type=inputs.boolean, help='Whether we work on a template or not', default=False, nullable=True)
|
||||
parser_get.add_argument('statictemplate', type=inputs.boolean, help='Whether we work on a static template or not', default=False, nullable=True)
|
||||
|
||||
@api.disabled_on_demo()
|
||||
@api.acl_admin_or_moderator_required(message=_('Sorry, you don\'t have rights to access the setting panel'))
|
||||
|
|
@ -528,7 +632,8 @@ class ClientSettings(Resource):
|
|||
|
||||
args = self.parser_post.parse_args()
|
||||
template = args.get('template', False)
|
||||
noti = bui.client.store_conf_cli(request.form, client, conf, template, server)
|
||||
statictemplate = args.get('statictemplate', False)
|
||||
noti = bui.client.store_conf_cli(request.form, client, conf, template, statictemplate, server)
|
||||
# clear cache
|
||||
cache.clear()
|
||||
# clear client-side cache through the _extra META variable
|
||||
|
|
@ -559,8 +664,9 @@ class ClientSettings(Resource):
|
|||
pass
|
||||
args = self.parser_get.parse_args()
|
||||
template = args.get('template', False)
|
||||
statictemplate = args.get('statictemplate', False)
|
||||
parser = bui.client.get_parser(agent=server)
|
||||
res = parser.read_client_conf(client, conf, template)
|
||||
res = parser.read_client_conf(client, conf, template, statictemplate)
|
||||
refresh()
|
||||
# Translate the doc and placeholder API side
|
||||
cache_keys = {
|
||||
|
|
@ -627,6 +733,7 @@ class ClientSettings(Resource):
|
|||
revoke = args.get('revoke', False)
|
||||
keepconf = args.get('keepconf', False)
|
||||
template = args.get('template', False)
|
||||
statictemplate = args.get('statictemplate', False)
|
||||
delete = args.get('delete', False)
|
||||
|
||||
if not keepconf:
|
||||
|
|
@ -647,8 +754,9 @@ class ClientSettings(Resource):
|
|||
bui.audit.logger.info(
|
||||
f'deleted client configuration {client}, delete certificate: {delcert}, '
|
||||
f'revoke certificate: {revoke}, keep a backup of the configuration: '
|
||||
f'{keepconf}, delete data: {delete}, is template: {template}', server=server)
|
||||
return parser.remove_client(client, keepconf, delcert, revoke, template, delete), 200
|
||||
f'{keepconf}, delete data: {delete}, is template: {template} '
|
||||
f'is static template: {statictemplate}', server=server)
|
||||
return parser.remove_client(client, keepconf, delcert, revoke, template, statictemplate, delete), 200
|
||||
|
||||
@api.disabled_on_demo()
|
||||
@api.acl_admin_or_moderator_required(message=_('Sorry, you don\'t have rights to access the setting panel'))
|
||||
|
|
@ -677,6 +785,7 @@ class ClientSettings(Resource):
|
|||
keepcert = args.get('keepcert', False)
|
||||
keepdata = args.get('keepdata', False)
|
||||
template = args.get('template', False)
|
||||
statictemplate = args.get('statictemplate', False)
|
||||
|
||||
# clear the cache when we remove a client
|
||||
cache.clear()
|
||||
|
|
@ -695,8 +804,8 @@ class ClientSettings(Resource):
|
|||
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
|
||||
f'is template: {template}, is static template: {statictemplate}', server=server)
|
||||
return parser.rename_client(client, newname, template, statictemplate, keepcert, keepdata), 200
|
||||
|
||||
|
||||
@ns.route('/path-expander',
|
||||
|
|
|
|||
|
|
@ -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}
|
||||
|
|
|
|||
|
|
@ -39,12 +39,17 @@ class Parser(Doc):
|
|||
self._client_conf = {}
|
||||
self._clients_conf = {}
|
||||
self._templates_conf = {}
|
||||
self._static_templates_conf = {}
|
||||
self.clientconfdir = None
|
||||
self.clientconfdir_mtime = None
|
||||
self.templates = []
|
||||
self.templates_dir = '.buitemplates'
|
||||
self.templates_path = None
|
||||
self.templates_mtime = None
|
||||
self.static_templates = []
|
||||
self.static_templates_dir = '.buistatictemplates'
|
||||
self.static_templates_path = None
|
||||
self.static_templates_mtime = None
|
||||
self.filescache = {}
|
||||
self._configs = {}
|
||||
self.root = None
|
||||
|
|
@ -102,6 +107,7 @@ class Parser(Doc):
|
|||
if purge:
|
||||
self._cleanup()
|
||||
self._list_templates(True)
|
||||
self._list_static_templates(True)
|
||||
self._list_clients(True)
|
||||
|
||||
def _load_conf_srv(self):
|
||||
|
|
@ -115,6 +121,12 @@ class Parser(Doc):
|
|||
os.makedirs(self.templates_path, 0o755)
|
||||
except OSError as exp:
|
||||
self.logger.warning(str(exp))
|
||||
self.static_templates_path = os.path.join(self.clientconfdir, self.static_templates_dir)
|
||||
if not os.path.exists(self.static_templates_path):
|
||||
try:
|
||||
os.makedirs(self.static_templates_path, 0o755)
|
||||
except OSError as exp:
|
||||
self.logger.warning(str(exp))
|
||||
|
||||
def _load_conf_cli(self):
|
||||
"""Load the client configuration file"""
|
||||
|
|
@ -156,6 +168,22 @@ class Parser(Doc):
|
|||
conf.parse()
|
||||
self._templates_conf[template['name']] = conf
|
||||
|
||||
def _load_conf_static_templates(self, name=None, in_path=None):
|
||||
"""Load all static templates configuration"""
|
||||
if name:
|
||||
templates = [{'name': name, 'value': in_path}]
|
||||
else:
|
||||
templates = self._list_static_templates(True)
|
||||
|
||||
for template in templates:
|
||||
conf = self.server_conf.clone()
|
||||
path = os.path.join(self.static_templates_path, template['name'])
|
||||
if template['name'] not in self._static_templates_conf:
|
||||
conf.add_file(path)
|
||||
conf.set_default(path)
|
||||
conf.parse()
|
||||
self._static_templates_conf[template['name']] = conf
|
||||
|
||||
def _load_all_conf(self):
|
||||
"""Load all configurations"""
|
||||
self._cleanup()
|
||||
|
|
@ -174,6 +202,11 @@ class Parser(Doc):
|
|||
self._load_conf_templates(name, path)
|
||||
return self._templates_conf[name]
|
||||
|
||||
def _new_static_template_conf(self, name, path):
|
||||
"""Create new static template conf"""
|
||||
self._load_conf_static_templates(name, path)
|
||||
return self._static_templates_conf[name]
|
||||
|
||||
def _clientconfdir_changed(self):
|
||||
"""Detect changes in clientconfdir"""
|
||||
if not self.clientconfdir:
|
||||
|
|
@ -196,6 +229,17 @@ class Parser(Doc):
|
|||
return True
|
||||
return False
|
||||
|
||||
def _static_templates_changed(self):
|
||||
"""Detect changes in static_templates_dir"""
|
||||
if not self.static_templates_path:
|
||||
return False
|
||||
mtime = os.path.getmtime(self.static_templates_path)
|
||||
changed = mtime != self.static_templates_mtime
|
||||
if changed:
|
||||
self.static_templates_mtime = mtime
|
||||
return True
|
||||
return False
|
||||
|
||||
def _get_client(self, name, path):
|
||||
"""Return client conf and refresh it if necessary
|
||||
|
||||
|
|
@ -220,9 +264,25 @@ class Parser(Doc):
|
|||
self._load_conf_templates()
|
||||
if name not in self._templates_conf:
|
||||
return self._new_template_conf(name, path)
|
||||
if self._templates_conf[name].changed:
|
||||
self._templates_conf[name].parse()
|
||||
return self._templates_conf[name]
|
||||
template = self._templates_conf[name]
|
||||
if template.changed:
|
||||
template.parse()
|
||||
return template
|
||||
|
||||
def _get_static_template(self, name, path=None):
|
||||
"""Return static template conf and refresh it if necessary
|
||||
|
||||
:rtype: Config
|
||||
"""
|
||||
if self._static_templates_changed() and name not in self._static_templates_conf:
|
||||
self._static_templates_conf.clear()
|
||||
self._load_conf_static_templates()
|
||||
if name not in self._static_templates_conf:
|
||||
return self._new_static_template_conf(name, path)
|
||||
template = self._static_templates_conf[name]
|
||||
if template.changed:
|
||||
template.parse()
|
||||
return template
|
||||
|
||||
def _get_config(self, path, mode='cli'):
|
||||
"""Return conf by it's path
|
||||
|
|
@ -298,6 +358,35 @@ class Parser(Doc):
|
|||
self.templates_mtime = os.path.getmtime(self.templates_path)
|
||||
return res
|
||||
|
||||
def _list_static_templates(self, force=False):
|
||||
res = []
|
||||
if not self.clientconfdir or not os.path.isdir(self.static_templates_path):
|
||||
return res
|
||||
|
||||
if self.static_templates and not force and \
|
||||
not self._clientconfdir_changed() and \
|
||||
not self._static_templates_changed():
|
||||
return self.static_templates
|
||||
|
||||
for tpl in os.listdir(self.static_templates_path):
|
||||
full_file = os.path.join(self.static_templates_path, tpl)
|
||||
if os.path.isfile(full_file) and not tpl.startswith('.') and \
|
||||
not tpl.endswith('~'):
|
||||
try:
|
||||
with open(full_file) as template:
|
||||
res.append({
|
||||
'name': tpl,
|
||||
'value': os.path.join(self.static_templates_dir, tpl),
|
||||
'content': template.read()
|
||||
})
|
||||
except OSError as exp:
|
||||
self.logger.warning(str(exp))
|
||||
|
||||
self.static_templates = res
|
||||
self.clientconfdir_mtime = os.path.getmtime(self.clientconfdir)
|
||||
self.static_templates_mtime = os.path.getmtime(self.static_templates_path)
|
||||
return res
|
||||
|
||||
def _get_server_path(self, name=None, fil=''):
|
||||
"""Returns the path of the 'server *fil*' file"""
|
||||
if not name:
|
||||
|
|
@ -355,7 +444,7 @@ class Parser(Doc):
|
|||
return self.openssl_auth.check_client_revoked(client)
|
||||
|
||||
def remove_client(self, client=None, keepconf=False, delcert=False, revoke=False,
|
||||
template=False, delete=False):
|
||||
template=False, statictemplate=False, delete=False):
|
||||
"""See :func:`burpui.misc.parser.interface.BUIparser.remove_client`"""
|
||||
res = []
|
||||
revoked = False
|
||||
|
|
@ -367,16 +456,20 @@ class Parser(Doc):
|
|||
if not keepconf:
|
||||
if template:
|
||||
path = os.path.join(self.templates_path, client)
|
||||
elif statictemplate:
|
||||
path = os.path.join(self.static_templates_path, client)
|
||||
else:
|
||||
path = os.path.join(self.clientconfdir, client)
|
||||
os.unlink(path)
|
||||
res.append([NOTIF_OK, "'{}' successfully removed".format(client)])
|
||||
removed = True
|
||||
|
||||
if client in self._clients_conf and not template:
|
||||
if client in self._clients_conf and not (template or statictemplate):
|
||||
del self._clients_conf[client]
|
||||
elif template and client in self._templates_conf:
|
||||
del self._templates_conf[client]
|
||||
elif statictemplate and client in self._static_templates_conf:
|
||||
del self._static_templates_conf[client]
|
||||
if path in self.filescache:
|
||||
del self.filescache[path]
|
||||
|
||||
|
|
@ -412,8 +505,8 @@ class Parser(Doc):
|
|||
|
||||
return res
|
||||
|
||||
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):
|
||||
"""See :func:`burpui.misc.parser.interface.BUIparser.rename_client`"""
|
||||
res = []
|
||||
if not client:
|
||||
|
|
@ -424,6 +517,9 @@ class Parser(Doc):
|
|||
if template:
|
||||
path = os.path.join(self.templates_path, client)
|
||||
newpath = os.path.join(self.templates_path, newname)
|
||||
elif statictemplate:
|
||||
path = os.path.join(self.static_templates_path, client)
|
||||
newpath = os.path.join(self.static_templates_path, newname)
|
||||
else:
|
||||
data = self._get_server_path(client)
|
||||
conf = self.clients_conf.get(client)
|
||||
|
|
@ -436,16 +532,18 @@ class Parser(Doc):
|
|||
except OSError as exp:
|
||||
res.append([NOTIF_ERROR, str(exp)])
|
||||
|
||||
if client in self._clients_conf and not template:
|
||||
if client in self._clients_conf and not (template or statictemplate):
|
||||
del self._clients_conf[client]
|
||||
elif template and client in self._templates_conf:
|
||||
del self._templates_conf[client]
|
||||
elif statictemplate and client in self._static_templates_conf:
|
||||
del self._static_templates_conf[client]
|
||||
if path in self.filescache:
|
||||
del self.filescache[path]
|
||||
|
||||
self._refresh_cache()
|
||||
|
||||
if template:
|
||||
if template or statictemplate:
|
||||
return res
|
||||
|
||||
if keepdata:
|
||||
|
|
@ -485,7 +583,7 @@ class Parser(Doc):
|
|||
|
||||
return res
|
||||
|
||||
def read_client_conf(self, client=None, conf=None, template=False):
|
||||
def read_client_conf(self, client=None, conf=None, template=False, statictemplate=False):
|
||||
"""
|
||||
See :func:`burpui.misc.parser.interface.BUIparser.read_client_conf`
|
||||
"""
|
||||
|
|
@ -510,6 +608,9 @@ class Parser(Doc):
|
|||
if template:
|
||||
mconf = os.path.join(self.templates_path, client)
|
||||
config = self._get_template(client, mconf)
|
||||
elif statictemplate:
|
||||
mconf = os.path.join(self.static_templates_path, client)
|
||||
config = self._get_static_template(client, mconf)
|
||||
else:
|
||||
mconf = os.path.join(self.clientconfdir, client)
|
||||
config = self._get_client(client, mconf)
|
||||
|
|
@ -599,7 +700,13 @@ class Parser(Doc):
|
|||
self.read_server_conf()
|
||||
return self._list_templates()
|
||||
|
||||
def store_client_conf(self, data, client=None, conf=None, template=False):
|
||||
def list_static_templates(self):
|
||||
"""See :func:`burpui.misc.parser.interface.BUIparser.list_static_templates`"""
|
||||
self.read_server_conf()
|
||||
return self._list_static_templates()
|
||||
|
||||
def store_client_conf(self, data, client=None, conf=None, template=False,
|
||||
statictemplate=False, content=''):
|
||||
"""
|
||||
See :func:`burpui.misc.parser.interface.BUIparser.store_client_conf`
|
||||
"""
|
||||
|
|
@ -608,21 +715,30 @@ class Parser(Doc):
|
|||
if not conf and not client:
|
||||
if template:
|
||||
return [[NOTIF_ERROR, 'Sorry, no template defined']]
|
||||
elif statictemplate:
|
||||
return [[NOTIF_ERROR, 'Sorry, no static template defined']]
|
||||
return [[NOTIF_ERROR, 'Sorry, no client defined']]
|
||||
elif client and not conf:
|
||||
if template:
|
||||
if not self.templates_path:
|
||||
return [[NOTIF_ERROR, 'Sorry, no template directory found']]
|
||||
conf = os.path.join(self.templates_path, client)
|
||||
elif statictemplate:
|
||||
if not self.static_templates_path:
|
||||
return [[NOTIF_ERROR, 'Sorry, no static template directory found']]
|
||||
conf = os.path.join(self.static_templates_path, client)
|
||||
else:
|
||||
conf = os.path.join(self.clientconfdir, client)
|
||||
ret = self.store_conf(data, conf, client, mode='cli', template=template)
|
||||
ret = self.store_conf(data, conf, client, mode='cli', template=template,
|
||||
statictemplate=statictemplate, content=content)
|
||||
self._refresh_cache() # refresh client list
|
||||
return ret
|
||||
|
||||
def store_conf(self, data, conf=None, client=None, mode='srv',
|
||||
insecure=False, template=False):
|
||||
insecure=False, template=False, statictemplate=False,
|
||||
content=''):
|
||||
"""See :func:`burpui.misc.parser.interface.BUIparser.store_conf`"""
|
||||
ret = []
|
||||
mconf = None
|
||||
if not conf:
|
||||
mconf = self.conf
|
||||
|
|
@ -642,16 +758,42 @@ class Parser(Doc):
|
|||
]
|
||||
]
|
||||
|
||||
wrote_content = False
|
||||
if content:
|
||||
if os.path.exists(mconf):
|
||||
ret.append([NOTIF_WARN, 'The file already exists, we won\'t override it'])
|
||||
else:
|
||||
try:
|
||||
with open(mconf, 'w') as temp:
|
||||
if not content.endswith('\n'):
|
||||
content += '\n'
|
||||
temp.write(content)
|
||||
ret.append([NOTIF_OK, 'File successfully written from template'])
|
||||
wrote_content = True
|
||||
except IOError as exp:
|
||||
ret.append([NOTIF_WARN, str(exp)])
|
||||
|
||||
check = False
|
||||
if template:
|
||||
conffile = self._get_template(client, mconf).get_file(mconf)
|
||||
elif statictemplate:
|
||||
conffile = self._get_static_template(client, mconf).get_file(mconf)
|
||||
elif client:
|
||||
conffile = self._get_client(client, mconf).get_file(mconf)
|
||||
else:
|
||||
conffile = self.server_conf.get_file(mconf)
|
||||
check = True
|
||||
|
||||
ret = conffile.store_data(data, insecure)
|
||||
if wrote_content and data:
|
||||
for key in data.keys():
|
||||
tmp = data.getlist(key)
|
||||
if len(tmp) == 1:
|
||||
conffile[key] = tmp[0]
|
||||
else:
|
||||
conffile[key] = tmp
|
||||
ret += conffile.store(insecure=insecure)
|
||||
elif not wrote_content:
|
||||
ret += conffile.store_data(data, insecure)
|
||||
|
||||
if check:
|
||||
clientconfdir = conffile.get('clientconfdir')
|
||||
|
|
|
|||
|
|
@ -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> {{ _("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> {{ _("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
|
||||
------
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue