burp-ui/burpui/api/client.py
2023-03-19 15:13:01 +01:00

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