mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-15 06:05:58 -06:00
1096 lines
36 KiB
Python
1096 lines
36 KiB
Python
# -*- coding: utf8 -*-
|
|
"""
|
|
.. module:: burpui.api.client
|
|
:platform: Unix
|
|
:synopsis: Burp-UI client api module.
|
|
|
|
.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>
|
|
|
|
"""
|
|
import os
|
|
import re
|
|
|
|
from flask import current_app, request
|
|
from flask_login import current_user
|
|
from flask_restx import inputs
|
|
from flask_restx.marshalling import marshal
|
|
|
|
from ..decorators import browser_cache
|
|
from ..engines.server import BUIServer # noqa
|
|
from ..exceptions import BUIserverException
|
|
from ..ext.cache import cache
|
|
from . import api, cache_key, force_refresh
|
|
from .custom import Resource, fields
|
|
|
|
bui = current_app # type: BUIServer
|
|
ns = api.namespace("client", "Client methods")
|
|
|
|
node_fields = ns.model(
|
|
"ClientTree",
|
|
{
|
|
"date": fields.DateTime(
|
|
required=True,
|
|
dt_format="iso8601",
|
|
description="Human representation of the backup date",
|
|
),
|
|
"gid": fields.Integer(required=True, description="gid owner of the node"),
|
|
"inodes": fields.Integer(required=True, description="Inodes of the node"),
|
|
"mode": fields.String(
|
|
required=True, description='Human readable mode. Example: "drwxr-xr-x"'
|
|
),
|
|
"name": fields.String(required=True, description="Node name"),
|
|
"title": fields.SafeString(
|
|
required=True, description="Node name (alias)", attribute="name"
|
|
),
|
|
"fullname": fields.String(required=True, description="Full name of the Node"),
|
|
"key": fields.String(
|
|
required=True,
|
|
description="Full name of the Node (alias)",
|
|
attribute="fullname",
|
|
),
|
|
"parent": fields.String(required=True, description="Parent node name"),
|
|
"size": fields.String(
|
|
required=True, description='Human readable size. Example: "12.0KiB"'
|
|
),
|
|
"type": fields.String(required=True, description='Node type. Example: "d"'),
|
|
"uid": fields.Integer(required=True, description="uid owner of the node"),
|
|
"selected": fields.Boolean(
|
|
required=False, description="Is path selected", default=False
|
|
),
|
|
"lazy": fields.Boolean(
|
|
required=False,
|
|
description="Do the children have been loaded during this"
|
|
+ " request or not",
|
|
default=True,
|
|
),
|
|
"folder": fields.Boolean(required=True, description="Is it a folder"),
|
|
"expanded": fields.Boolean(
|
|
required=False, description="Should we expand the node", default=False
|
|
),
|
|
# Cannot use nested on own
|
|
"children": fields.Raw(required=False, description="List of children"),
|
|
},
|
|
)
|
|
|
|
|
|
@ns.route(
|
|
"/browse/<name>/<int:backup>",
|
|
"/<server>/browse/<name>/<int:backup>",
|
|
endpoint="client_tree",
|
|
)
|
|
@ns.doc(
|
|
params={
|
|
"server": "Which server to collect data from when in" + " multi-agent mode",
|
|
"name": "Client name",
|
|
"backup": "Backup number",
|
|
},
|
|
)
|
|
class ClientTree(Resource):
|
|
"""The :class:`burpui.api.client.ClientTree` resource allows you to
|
|
retrieve a list of files in a given backup.
|
|
|
|
This resource is part of the :mod:`burpui.api.client` module.
|
|
|
|
An optional ``GET`` parameter called ``serverName`` is supported when
|
|
running in multi-agent mode.
|
|
A mandatory ``GET`` parameter called ``root`` is used to know what path we
|
|
are working on.
|
|
"""
|
|
|
|
parser = ns.parser()
|
|
parser.add_argument(
|
|
"serverName", help="Which server to collect data from when in multi-agent mode"
|
|
)
|
|
parser.add_argument(
|
|
"root",
|
|
help="Root path to expand. You may specify several of them",
|
|
action="append",
|
|
)
|
|
parser.add_argument(
|
|
"recursive",
|
|
type=inputs.boolean,
|
|
help="Returns the whole tree instead of just the sub-tree",
|
|
nullable=True,
|
|
required=False,
|
|
default=False,
|
|
)
|
|
parser.add_argument(
|
|
"selected",
|
|
type=inputs.boolean,
|
|
help="Make the returned path selected at load time. Only works"
|
|
+ " if 'recursive' is True",
|
|
nullable=True,
|
|
required=False,
|
|
default=False,
|
|
)
|
|
parser.add_argument(
|
|
"init",
|
|
type=inputs.boolean,
|
|
help="First call to load the root of the tree",
|
|
nullable=True,
|
|
required=False,
|
|
default=False,
|
|
)
|
|
|
|
@cache.cached(timeout=3600, key_prefix=cache_key, unless=force_refresh)
|
|
@ns.marshal_list_with(node_fields, code=200, description="Success")
|
|
@ns.expect(parser)
|
|
@ns.doc(
|
|
responses={
|
|
"403": "Insufficient permissions",
|
|
"500": "Internal failure",
|
|
},
|
|
)
|
|
@browser_cache(3600)
|
|
def get(self, server=None, name=None, backup=None):
|
|
"""Returns a list of 'nodes' under a given path
|
|
|
|
**GET** method provided by the webservice.
|
|
|
|
The *JSON* returned is:
|
|
::
|
|
|
|
[
|
|
{
|
|
"date": "2015-05-21 14:54:49",
|
|
"gid": "0",
|
|
"inodes": "173",
|
|
"selected": false,
|
|
"expanded": false,
|
|
"children": [],
|
|
"mode": "drwxr-xr-x",
|
|
"name": "/",
|
|
"key": "/",
|
|
"title": "/",
|
|
"fullname": "/",
|
|
"parent": "",
|
|
"size": "12.0KiB",
|
|
"type": "d",
|
|
"uid": "0"
|
|
}
|
|
]
|
|
|
|
|
|
The output is filtered by the :mod:`burpui.misc.acl` module so that you
|
|
only see stats about the clients you are authorized 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
|
|
|
|
:returns: The *JSON* described above.
|
|
"""
|
|
args = self.parser.parse_args()
|
|
server = server or args["serverName"]
|
|
json = []
|
|
if not name or not backup:
|
|
return json
|
|
|
|
root_list = sorted(args["root"]) if args["root"] else []
|
|
root_loaded = False
|
|
paths_loaded = []
|
|
to_select_list = []
|
|
|
|
if (
|
|
not current_user.is_anonymous
|
|
and not current_user.acl.is_admin()
|
|
and not current_user.acl.is_client_allowed(name, server)
|
|
):
|
|
self.abort(403, "Sorry, you are not allowed to view this client")
|
|
|
|
from_cookie = None
|
|
if args["init"] and not root_list:
|
|
from_cookie = request.cookies.get("fancytree-1-expanded", "")
|
|
if from_cookie:
|
|
args["recursive"] = True
|
|
_root = bui.client.get_tree(name, backup, agent=server)
|
|
root_list = [x["name"] for x in _root]
|
|
for path in from_cookie.split("~"):
|
|
if not path.endswith("/"):
|
|
path += "/"
|
|
if path not in root_list:
|
|
root_list.append(path)
|
|
root_list = sorted(root_list)
|
|
|
|
try:
|
|
root_list_clean = []
|
|
|
|
for root in root_list:
|
|
if args["recursive"]:
|
|
path = ""
|
|
# fetch the root first if not already loaded
|
|
if not root_loaded:
|
|
part = bui.client.get_tree(name, backup, level=0, agent=server)
|
|
root_loaded = True
|
|
else:
|
|
part = []
|
|
root = root.rstrip("/")
|
|
to_select = root.rsplit("/", 1)
|
|
if not to_select[0]:
|
|
to_select[0] = "/"
|
|
if len(to_select) == 1:
|
|
# special case we want to select '/'
|
|
to_select = ("", "/")
|
|
if not root:
|
|
root = "/"
|
|
to_select_list.append(to_select)
|
|
root_list_clean.append(root)
|
|
paths = root.split("/")
|
|
for level, sub in enumerate(paths, start=1):
|
|
path = os.path.join(path, sub)
|
|
if not path:
|
|
path = "/"
|
|
if path in paths_loaded:
|
|
continue
|
|
temp = bui.client.get_tree(
|
|
name, backup, path, level, agent=server
|
|
)
|
|
paths_loaded.append(path)
|
|
part += temp
|
|
else:
|
|
part = bui.client.get_tree(name, backup, root, agent=server)
|
|
json += part
|
|
|
|
if args["selected"]:
|
|
for entry in json:
|
|
for parent, fold in to_select_list:
|
|
if entry["parent"] == parent and entry["name"] == fold:
|
|
entry["selected"] = True
|
|
break
|
|
if entry["parent"] in root_list_clean:
|
|
entry["selected"] = True
|
|
|
|
if not root_list:
|
|
json = bui.client.get_tree(name, backup, agent=server)
|
|
if args["selected"]:
|
|
for entry in json:
|
|
if not entry["parent"]:
|
|
entry["selected"] = True
|
|
|
|
if args["recursive"]:
|
|
tree = {}
|
|
rjson = []
|
|
roots = []
|
|
for entry in json:
|
|
# /!\ after marshalling, 'fullname' will be 'key'
|
|
tree[entry["fullname"]] = marshal(entry, node_fields)
|
|
|
|
for key, entry in tree.items():
|
|
parent = entry["parent"]
|
|
if not entry["children"]:
|
|
entry["children"] = None
|
|
if parent:
|
|
node = tree[parent]
|
|
if not node["children"]:
|
|
node["children"] = []
|
|
node["children"].append(entry)
|
|
if node["folder"]:
|
|
node["lazy"] = False
|
|
node["expanded"] = True
|
|
else:
|
|
roots.append(entry["key"])
|
|
|
|
for fullname in roots:
|
|
rjson.append(tree[fullname])
|
|
|
|
json = rjson
|
|
else:
|
|
for entry in json:
|
|
entry["children"] = None
|
|
if not entry["folder"]:
|
|
entry["lazy"] = False
|
|
|
|
except BUIserverException as e:
|
|
self.abort(500, str(e))
|
|
return json
|
|
|
|
|
|
@ns.route(
|
|
"/browseall/<name>/<int:backup>",
|
|
"/<server>/browseall/<name>/<int:backup>",
|
|
endpoint="client_tree_all",
|
|
)
|
|
@ns.doc(
|
|
params={
|
|
"server": "Which server to collect data from when in" + " multi-agent mode",
|
|
"name": "Client name",
|
|
"backup": "Backup number",
|
|
},
|
|
)
|
|
class ClientTreeAll(Resource):
|
|
"""The :class:`burpui.api.client.ClientTreeAll` resource allows you to
|
|
retrieve a list of all the files in a given backup.
|
|
|
|
This resource is part of the :mod:`burpui.api.client` module.
|
|
|
|
An optional ``GET`` parameter called ``serverName`` is supported when
|
|
running in multi-agent mode.
|
|
"""
|
|
|
|
parser = ns.parser()
|
|
parser.add_argument(
|
|
"serverName", help="Which server to collect data from when in multi-agent mode"
|
|
)
|
|
|
|
@cache.cached(timeout=3600, key_prefix=cache_key, unless=force_refresh)
|
|
@ns.marshal_list_with(node_fields, code=200, description="Success")
|
|
@ns.expect(parser)
|
|
@ns.doc(
|
|
responses={
|
|
"403": "Insufficient permissions",
|
|
"405": "Method not allowed",
|
|
"500": "Internal failure",
|
|
},
|
|
)
|
|
@browser_cache(3600)
|
|
def get(self, server=None, name=None, backup=None):
|
|
"""Returns a list of all 'nodes' of a given backup
|
|
|
|
**GET** method provided by the webservice.
|
|
|
|
The *JSON* returned is:
|
|
::
|
|
|
|
[
|
|
{
|
|
"date": "2015-05-21 14:54:49",
|
|
"gid": "0",
|
|
"inodes": "173",
|
|
"selected": false,
|
|
"expanded": false,
|
|
"children": [],
|
|
"mode": "drwxr-xr-x",
|
|
"name": "/",
|
|
"key": "/",
|
|
"title": "/",
|
|
"fullname": "/",
|
|
"parent": "",
|
|
"size": "12.0KiB",
|
|
"type": "d",
|
|
"uid": "0"
|
|
}
|
|
]
|
|
|
|
|
|
The output is filtered by the :mod:`burpui.misc.acl` module so that you
|
|
only see stats about the clients you are authorized 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
|
|
|
|
:returns: The *JSON* described above.
|
|
"""
|
|
args = self.parser.parse_args()
|
|
server = server or args["serverName"]
|
|
|
|
if not bui.client.get_attr("batch_list_supported", False, server):
|
|
self.abort(405, "Sorry, the requested backend does not support this method")
|
|
|
|
if (
|
|
not current_user.is_anonymous
|
|
and not current_user.acl.is_admin()
|
|
and not current_user.acl.is_client_allowed(name, server)
|
|
):
|
|
self.abort(403, "Sorry, you are not allowed to view this client")
|
|
|
|
try:
|
|
json = self._get_tree_all(name, backup, server)
|
|
except BUIserverException as e:
|
|
self.abort(500, str(e))
|
|
return json
|
|
|
|
@staticmethod
|
|
def _get_tree_all(name, backup, server):
|
|
json = bui.client.get_tree(name, backup, "*", agent=server)
|
|
tree = {}
|
|
rjson = []
|
|
roots = []
|
|
|
|
def __expand_json(js):
|
|
res = {}
|
|
for entry in js:
|
|
# /!\ after marshalling, 'fullname' will be 'key'
|
|
res[entry["fullname"]] = marshal(entry, node_fields)
|
|
return res
|
|
|
|
tree = __expand_json(json)
|
|
|
|
# TODO: we can probably improve this at some point
|
|
redo = True
|
|
while redo:
|
|
redo = False
|
|
# using a copy
|
|
for key, entry in dict(tree).items():
|
|
parent = entry["parent"]
|
|
if not entry["children"]:
|
|
entry["children"] = None
|
|
if parent:
|
|
if parent not in tree:
|
|
parent2 = parent
|
|
last = False
|
|
while parent not in tree and not last:
|
|
if not parent2:
|
|
last = True
|
|
json = bui.client.get_tree(
|
|
name, backup, parent2, agent=server
|
|
)
|
|
if parent2 == "/":
|
|
parent2 = ""
|
|
else:
|
|
parent2 = os.path.dirname(parent2)
|
|
tree2 = __expand_json(json)
|
|
tree.update(tree2)
|
|
roots = []
|
|
redo = True
|
|
break
|
|
node = tree[parent]
|
|
if not node["children"]:
|
|
node["children"] = []
|
|
elif entry in node["children"]:
|
|
continue
|
|
node["children"].append(entry)
|
|
if node["folder"]:
|
|
node["lazy"] = False
|
|
node["expanded"] = False
|
|
else:
|
|
roots.append(entry["key"])
|
|
|
|
for fullname in roots:
|
|
rjson.append(tree[fullname])
|
|
|
|
return rjson
|
|
|
|
|
|
@ns.route(
|
|
"/report/<name>",
|
|
"/<server>/report/<name>",
|
|
"/report/<name>/<int:backup>",
|
|
"/<server>/report/<name>/<int:backup>",
|
|
endpoint="client_report",
|
|
)
|
|
@ns.doc(
|
|
params={
|
|
"server": "Which server to collect data from when in multi-agent" + " mode",
|
|
"name": "Client name",
|
|
"backup": "Backup number",
|
|
},
|
|
)
|
|
class ClientReport(Resource):
|
|
"""The :class:`burpui.api.client.ClientStats` resource allows you to
|
|
retrieve a report on a given backup for a given client.
|
|
|
|
This resource is part of the :mod:`burpui.api.client` module.
|
|
|
|
An optional ``GET`` parameter called ``serverName`` is supported when
|
|
running in multi-agent mode.
|
|
"""
|
|
|
|
parser = ns.parser()
|
|
parser.add_argument(
|
|
"serverName", help="Which server to collect data from when in multi-agent mode"
|
|
)
|
|
report_tpl_fields = ns.model(
|
|
"ClientReportTpl",
|
|
{
|
|
"changed": fields.Integer(
|
|
required=True, description="Number of changed files", default=0
|
|
),
|
|
"deleted": fields.Integer(
|
|
required=True, description="Number of deleted files", default=0
|
|
),
|
|
"new": fields.Integer(
|
|
required=True, description="Number of new files", default=0
|
|
),
|
|
"scanned": fields.Integer(
|
|
required=True, description="Number of scanned files", default=0
|
|
),
|
|
"total": fields.Integer(
|
|
required=True, description="Total number of files", default=0
|
|
),
|
|
"unchanged": fields.Integer(
|
|
required=True, description="Number of scanned files", default=0
|
|
),
|
|
},
|
|
)
|
|
report_fields = ns.model(
|
|
"ClientReport",
|
|
{
|
|
"dir": fields.Nested(report_tpl_fields, required=True),
|
|
"duration": fields.Integer(
|
|
required=True, description="Backup duration in seconds"
|
|
),
|
|
"efs": fields.Nested(report_tpl_fields, required=True),
|
|
"encrypted": fields.Boolean(
|
|
required=True, description="Is the backup encrypted"
|
|
),
|
|
"end": fields.DateTime(
|
|
dt_format="iso8601",
|
|
required=True,
|
|
description="Timestamp of the end date of the backup",
|
|
),
|
|
"files": fields.Nested(report_tpl_fields, required=True),
|
|
"files_enc": fields.Nested(report_tpl_fields, required=True),
|
|
"hardlink": fields.Nested(report_tpl_fields, required=True),
|
|
"meta": fields.Nested(report_tpl_fields, required=True),
|
|
"meta_enc": fields.Nested(report_tpl_fields, required=True),
|
|
"number": fields.Integer(required=True, description="Backup number"),
|
|
"received": fields.Integer(required=True, description="Bytes received"),
|
|
"softlink": fields.Nested(report_tpl_fields, required=True),
|
|
"special": fields.Nested(report_tpl_fields, required=True),
|
|
"start": fields.DateTime(
|
|
dt_format="iso8601",
|
|
required=True,
|
|
description="Timestamp of the beginning of the backup",
|
|
),
|
|
"totsize": fields.Integer(
|
|
required=True, description="Total size of the backup"
|
|
),
|
|
"vssfooter": fields.Nested(report_tpl_fields, required=True),
|
|
"vssfooter_enc": fields.Nested(report_tpl_fields, required=True),
|
|
"vssheader": fields.Nested(report_tpl_fields, required=True),
|
|
"vssheader_enc": fields.Nested(report_tpl_fields, required=True),
|
|
"windows": fields.Boolean(
|
|
required=True, description="Is the client a windows system"
|
|
),
|
|
},
|
|
)
|
|
|
|
@cache.cached(timeout=1800, key_prefix=cache_key, unless=force_refresh)
|
|
@ns.marshal_with(report_fields, code=200, description="Success")
|
|
@ns.expect(parser)
|
|
@ns.doc(
|
|
responses={
|
|
"403": "Insufficient permissions",
|
|
"500": "Internal failure",
|
|
},
|
|
)
|
|
@browser_cache(1800)
|
|
def get(self, server=None, name=None, backup=None):
|
|
"""Returns a global report of a given backup/client
|
|
|
|
**GET** method provided by the webservice.
|
|
|
|
The *JSON* returned is:
|
|
::
|
|
|
|
{
|
|
"dir": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 394,
|
|
"scanned": 394,
|
|
"total": 394,
|
|
"unchanged": 0
|
|
},
|
|
"duration": 5,
|
|
"efs": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"encrypted": true,
|
|
"end": 1422189124,
|
|
"files": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"files_enc": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 1421,
|
|
"scanned": 1421,
|
|
"total": 1421,
|
|
"unchanged": 0
|
|
},
|
|
"hardlink": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"meta": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"meta_enc": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"number": 1,
|
|
"received": 1679304,
|
|
"softlink": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 1302,
|
|
"scanned": 1302,
|
|
"total": 1302,
|
|
"unchanged": 0
|
|
},
|
|
"special": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"start": 1422189119,
|
|
"total": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 3117,
|
|
"scanned": 3117,
|
|
"total": 3117,
|
|
"unchanged": 0
|
|
},
|
|
"totsize": 5345361,
|
|
"vssfooter": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"vssfooter_enc": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"vssheader": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"vssheader_enc": {
|
|
"changed": 0,
|
|
"deleted": 0,
|
|
"new": 0,
|
|
"scanned": 0,
|
|
"total": 0,
|
|
"unchanged": 0
|
|
},
|
|
"windows": "false"
|
|
}
|
|
|
|
|
|
The output is filtered by the :mod:`burpui.misc.acl` module so that you
|
|
only see stats about the clients you are authorized 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
|
|
|
|
:returns: The *JSON* described above.
|
|
"""
|
|
server = server or self.parser.parse_args()["serverName"]
|
|
json = []
|
|
if not name:
|
|
err = [[1, "No client defined"]]
|
|
self.abort(400, err)
|
|
|
|
if (
|
|
not current_user.is_anonymous
|
|
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 to view this client report")
|
|
if backup:
|
|
try:
|
|
json = bui.client.get_backup_logs(backup, name, agent=server)
|
|
except BUIserverException as exp:
|
|
self.abort(500, str(exp))
|
|
else:
|
|
try:
|
|
json = bui.client.get_backup_logs(-1, name, agent=server)
|
|
except BUIserverException as exp:
|
|
self.abort(500, str(exp))
|
|
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 (
|
|
not current_user.is_anonymous
|
|
and not current_user.acl.is_admin()
|
|
and (
|
|
not current_user.acl.is_moderator()
|
|
or current_user.acl.is_moderator()
|
|
and not current_user.acl.is_client_rw(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)
|
|
bui.audit.logger.info(
|
|
f"requested the deletion of backup {backup} for {name}", server=server
|
|
)
|
|
return 202, ""
|
|
|
|
|
|
@ns.route("/stats/<name>", "/<server>/stats/<name>", endpoint="client_stats")
|
|
@ns.doc(
|
|
params={
|
|
"server": "Which server to collect data from when in multi-agent" + " mode",
|
|
"name": "Client name",
|
|
},
|
|
)
|
|
class ClientStats(Resource):
|
|
"""The :class:`burpui.api.client.ClientReport` resource allows you to
|
|
retrieve a list of backups for a given client.
|
|
|
|
This resource is part of the :mod:`burpui.api.client` module.
|
|
|
|
An optional ``GET`` parameter called ``serverName`` is supported when
|
|
running in multi-agent mode.
|
|
"""
|
|
|
|
parser = ns.parser()
|
|
parser.add_argument(
|
|
"serverName", help="Which server to collect data from when in multi-agent mode"
|
|
)
|
|
client_fields = ns.model(
|
|
"ClientStats",
|
|
{
|
|
"number": fields.Integer(required=True, description="Backup number"),
|
|
"received": fields.Integer(required=True, description="Bytes received"),
|
|
"size": fields.Integer(required=True, description="Total size"),
|
|
"encrypted": fields.Boolean(
|
|
required=True, description="Is the backup encrypted"
|
|
),
|
|
"deletable": fields.Boolean(
|
|
required=True, description="Is the backup deletable"
|
|
),
|
|
"date": fields.DateTime(
|
|
required=True,
|
|
dt_format="iso8601",
|
|
description="Human representation of the backup date",
|
|
),
|
|
},
|
|
)
|
|
|
|
@cache.cached(timeout=1800, key_prefix=cache_key, unless=force_refresh)
|
|
@ns.marshal_list_with(client_fields, code=200, description="Success")
|
|
@ns.expect(parser)
|
|
@ns.doc(
|
|
responses={
|
|
"403": "Insufficient permissions",
|
|
"500": "Internal failure",
|
|
},
|
|
)
|
|
@browser_cache(1800)
|
|
def get(self, server=None, name=None):
|
|
"""Returns a list of backups for a given client
|
|
|
|
**GET** method provided by the webservice.
|
|
|
|
The *JSON* returned is:
|
|
::
|
|
|
|
[
|
|
{
|
|
"date": "2015-01-25 13:32:00",
|
|
"deletable": true,
|
|
"encrypted": true,
|
|
"received": 123,
|
|
"size": 1234,
|
|
"number": 1
|
|
},
|
|
]
|
|
|
|
The output is filtered by the :mod:`burpui.misc.acl` module so that you
|
|
only see stats about the clients you are authorized 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
|
|
|
|
:returns: The *JSON* described above.
|
|
"""
|
|
server = server or self.parser.parse_args()["serverName"]
|
|
try:
|
|
if (
|
|
not current_user.is_anonymous
|
|
and 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")
|
|
json = bui.client.get_client(name, agent=server)
|
|
except BUIserverException as exp:
|
|
self.abort(500, str(exp))
|
|
return json
|
|
|
|
|
|
@ns.route("/labels/<name>", "/<server>/labels/<name>", endpoint="client_labels")
|
|
@ns.doc(
|
|
params={
|
|
"server": "Which server to collect data from when in multi-agent" + " mode",
|
|
"name": "Client name",
|
|
},
|
|
)
|
|
class ClientLabels(Resource):
|
|
"""The :class:`burpui.api.client.ClientLabels` resource allows you to
|
|
retrieve the labels of a given client.
|
|
|
|
This resource is part of the :mod:`burpui.api.client` module.
|
|
|
|
An optional ``GET`` parameter called ``serverName`` is supported when
|
|
running in multi-agent mode.
|
|
"""
|
|
|
|
parser = ns.parser()
|
|
parser.add_argument(
|
|
"serverName", help="Which server to collect data from when in multi-agent mode"
|
|
)
|
|
parser.add_argument("clientName", help="Client name")
|
|
labels_fields = ns.model(
|
|
"ClientLabels",
|
|
{
|
|
"labels": fields.List(fields.String, description="List of labels"),
|
|
},
|
|
)
|
|
|
|
@cache.cached(timeout=1800, key_prefix=cache_key, unless=force_refresh)
|
|
@ns.marshal_list_with(labels_fields, code=200, description="Success")
|
|
@ns.expect(parser)
|
|
@ns.doc(
|
|
responses={
|
|
"403": "Insufficient permissions",
|
|
"500": "Internal failure",
|
|
},
|
|
)
|
|
@browser_cache(1800)
|
|
def get(self, server=None, name=None):
|
|
"""Returns the labels of a given client
|
|
|
|
**GET** method provided by the webservice.
|
|
|
|
The *JSON* returned is:
|
|
::
|
|
|
|
{
|
|
"labels": [
|
|
"label1",
|
|
"label2"
|
|
]
|
|
}
|
|
|
|
The output is filtered by the :mod:`burpui.misc.acl` module so that you
|
|
only see stats about the clients you are authorized 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
|
|
|
|
:returns: The *JSON* described above.
|
|
"""
|
|
try:
|
|
if (
|
|
not current_user.is_anonymous
|
|
and 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")
|
|
labels = self._get_labels(name, server)
|
|
except BUIserverException as exp:
|
|
self.abort(500, str(exp))
|
|
return {"labels": labels}
|
|
|
|
@staticmethod
|
|
def _get_labels(client, server=None):
|
|
key = "labels-{}-{}".format(client, server)
|
|
ret = cache.cache.get(key)
|
|
if ret is not None:
|
|
return ret
|
|
labels = bui.client.get_client_labels(client, agent=server)
|
|
ret = []
|
|
ignore = re.compile("|".join(bui.ignore_labels)) if bui.ignore_labels else None
|
|
reformat = (
|
|
[(re.compile(regex), replace) for regex, replace in bui.format_labels]
|
|
if bui.format_labels
|
|
else []
|
|
)
|
|
for label in labels:
|
|
if bui.ignore_labels and ignore.search(label):
|
|
continue
|
|
tmp_label = label
|
|
for regex, replace in reformat:
|
|
tmp_label = regex.sub(replace, tmp_label)
|
|
ret.append(tmp_label)
|
|
cache.cache.set(key, ret, 1800)
|
|
return ret
|
|
|
|
|
|
@ns.route(
|
|
"/running",
|
|
"/running/<name>",
|
|
"/<server>/running",
|
|
"/<server>/running/<name>",
|
|
endpoint="client_running_status",
|
|
)
|
|
@ns.doc(
|
|
params={
|
|
"server": "Which server to collect data from when in multi-agent" + " mode",
|
|
"name": "Client name",
|
|
},
|
|
)
|
|
class ClientRunningStatus(Resource):
|
|
"""The :class:`burpui.api.client.ClientRunningStatus` resource allows you to
|
|
retrieve the running status of a given client.
|
|
|
|
This resource is part of the :mod:`burpui.api.client` module.
|
|
|
|
An optional ``GET`` parameter called ``serverName`` is supported when
|
|
running in multi-agent mode.
|
|
"""
|
|
|
|
parser = ns.parser()
|
|
parser.add_argument(
|
|
"serverName", help="Which server to collect data from when in multi-agent mode"
|
|
)
|
|
parser.add_argument("clientName", help="Client name")
|
|
running_fields = ns.model(
|
|
"ClientRunningStatus",
|
|
{
|
|
"state": fields.LocalizedString(required=True, description="Running state"),
|
|
"percent": fields.Integer(
|
|
required=False, description="Backup progress in percent", default=-1
|
|
),
|
|
"phase": fields.String(
|
|
required=False, description="Backup phase", default=None
|
|
),
|
|
"last": fields.DateTime(
|
|
required=False, dt_format="iso8601", description="Date of last backup"
|
|
),
|
|
},
|
|
)
|
|
|
|
@ns.marshal_list_with(running_fields, code=200, description="Success")
|
|
@ns.expect(parser)
|
|
@ns.doc(
|
|
responses={
|
|
"403": "Insufficient permissions",
|
|
"500": "Internal failure",
|
|
},
|
|
)
|
|
def get(self, server=None, name=None):
|
|
"""Returns the running status of a given client
|
|
|
|
**GET** method provided by the webservice.
|
|
|
|
The *JSON* returned is:
|
|
::
|
|
|
|
{
|
|
"state": "running",
|
|
"percent": 42,
|
|
"phase": "2",
|
|
"last": "now"
|
|
}
|
|
|
|
The output is filtered by the :mod:`burpui.misc.acl` module so that you
|
|
only see stats about the clients you are authorized 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
|
|
|
|
:returns: The *JSON* described above.
|
|
"""
|
|
args = self.parser.parse_args()
|
|
server = server or args["serverName"]
|
|
name = name or args["clientName"]
|
|
try:
|
|
if (
|
|
not current_user.is_anonymous
|
|
and 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")
|
|
json = bui.client.get_client_status(name, agent=server)
|
|
except BUIserverException as exp:
|
|
self.abort(500, str(exp))
|
|
return json
|