# -*- coding: utf8 -*-
"""
.. module:: burpui.api.settings
    :platform: Unix
    :synopsis: Burp-UI settings api module.

.. moduleauthor:: Ziirish <hi+burpui@ziirish.me>

"""
import json

from . import api
from ..engines.server import BUIServer  # noqa
from ..ext.cache import cache
from .custom import Resource
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,
    render_template_string,
)
from flask_login import current_user
from flask_restx 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")


@ns.route(
    "/server-config",
    "/<server>/server-config",
    "/server-config/<path:conf>",
    "/<server>/server-config/<path:conf>",
    endpoint="server_settings",
)
@ns.doc(
    params={
        "conf": "Path of the configuration file",
        "server": "Which server to collect data from when in multi-agent mode",
    },
)
class ServerSettings(Resource):
    """The :class:`burpui.api.settings.ServerSettings` resource allows you to
    read and write the server's configuration.

    This resource is part of the :mod:`burpui.api.settings` module.
    """

    @api.disabled_on_demo()
    @api.acl_admin_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 post(self, conf=None, server=None):
        """Saves the server configuration"""
        noti = bui.client.store_conf_srv(request.form, conf, server)
        bui.audit.logger.info(
            f"updated burp-server configuration ({conf})", server=server
        )
        return {"notif": noti}, 200

    @api.disabled_on_demo()
    @api.acl_admin_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 delete(self, conf=None, server=None):
        """Deletes a configuration file"""
        try:
            conf = unquote(conf)
        except:
            pass
        parser = bui.client.get_parser(agent=server)
        bui.audit.logger.info(f"requested removal of {conf}", server=server)
        return parser.remove_conf(conf)

    @api.acl_admin_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, conf=None, server=None):
        """Reads the server configuration

        **GET** method provided by the webservice.

        The *JSON* returned is:
        ::

            {
              "boolean": [
                "daemon",
                "fork",
                "..."
              ],
              "defaults": {
                "address": "",
                "autoupgrade_dir": "",
                "ca_burp_ca": "",
                "ca_conf": "",
                "ca_name": "",
                "ca_server_name": "",
                "client_can_delete": true,
                "...": "..."
              },
              "integer": [
                "port",
                "status_port",
                "..."
              ],
              "multi": [
                "keep",
                "restore_client",
                "..."
              ],
              "placeholders": {
                "autoupgrade_dir": "path",
                "ca_burp_ca": "path",
                "ca_conf": "path",
                "ca_name": "name",
                "ca_server_name": "name",
                "client_can_delete": "0|1",
                "...": "..."
              },
              "results": {
                "boolean": [
                  {
                    "name": "hardlinked_archive",
                    "value": false,
                    "reset": false
                  },
                  {
                    "name": "syslog",
                    "value": true,
                    "reset": false
                  },
                  { "...": "..." }
                ],
                "clients": [
                  {
                    "name": "testclient",
                    "value": "/etc/burp/clientconfdir/testclient"
                  }
                ],
                "common": [
                  {
                    "name": "mode",
                    "value": "server",
                    "reset": false
                  },
                  {
                    "name": "directory",
                    "value": "/var/spool/burp",
                    "reset": false
                  },
                  { "...": "..." }
                ],
                "includes": [],
                "includes_ext": [],
                "integer": [
                  {
                    "name": "port",
                    "value": 4971,
                    "reset": false
                  },
                  {
                    "name": "status_port",
                    "value": 4972,
                    "reset": false
                  },
                  { "...": "..." }
                ],
                "multi": [
                  {
                    "name": "keep",
                    "value": [
                      "7",
                      "4"
                    ],
                    "reset": [
                      false,
                      true
                    ]
                  },
                  { "...": "..." }
                ],
                "hierarchy": [
                  {
                    "children": [
                      {
                        "children": [],
                        "dir": "/tmp/burp/conf.d",
                        "full": "/tmp/burp/conf.d/empty.conf",
                        "name": "empty.conf",
                        "parent": "/tmp/burp/burp-server.conf"
                      },
                      {
                        "children": [],
                        "dir": "/tmp/burp/conf.d",
                        "full": "/tmp/burp/conf.d/ipv4.conf",
                        "name": "ipv4.conf",
                        "parent": "/tmp/burp/burp-server.conf"
                      }
                    ],
                    "dir": "/tmp/burp",
                    "full": "/tmp/burp/burp-server.conf",
                    "name": "burp-server.conf",
                    "parent": null
                  }
                ]
              },
              "server_doc": {
                "address": "Defines the main TCP address...",
                "...": "..."
              },
              "string": [
                "mode",
                "address",
                "..."
              ],
              "suggest": {
                "compression": [
                  "gzip1",
                  "gzip2",
                  "gzip3",
                  "gzip4",
                  "gzip5",
                  "gzip6",
                  "gzip7",
                  "gzip8",
                  "gzip9"
                ],
                "mode": [
                  "client",
                  "server"
                ],
                "...": [
                  "..."
                ]
              }
            }


        :param server: Which server to collect data from when in multi-agent mode
        :type server: str

        :returns: The *JSON* described above.
        """
        try:
            conf = unquote(conf)
        except:
            pass
        res = bui.client.read_conf_srv(conf, server)
        refresh()
        # Translate the doc and placeholder API side
        cache_keys = {
            "doc": "_doc_parser_{}-{}".format(server, g.locale),
            "placeholders": "_placeholders_parser_{}-{}".format(server, g.locale),
            "boolean_srv": "_boolean_srv_parser_{}".format(server),
            "string_srv": "_string_srv_parser_{}".format(server),
            "integer_srv": "_integer_srv_parser_{}".format(server),
            "multi_srv": "_multi_srv_parser_{}".format(server),
            "values": "_suggest_parser_{}".format(server),
            "defaults": "_defaults_parser_{}".format(server),
            "advanced_type": "_advanced_parser_{}".format(server),
            "pair_associations": "_pair_associations_parser_{}".format(server),
        }
        cache_results = {}
        for name, key in cache_keys.items():
            if not cache.cache.has(key):
                if name in ["doc", "placeholders"]:
                    _tmp = bui.client.get_parser_attr(name, server).copy()
                    _tmp2 = {}
                    for k, v in _tmp.items():
                        _tmp2[k] = _(v)
                    cache_results[name] = _tmp2
                else:
                    cache_results[name] = bui.client.get_parser_attr(name, server)
                cache.cache.set(key, cache_results[name], 3600)
            else:
                cache_results[name] = cache.cache.get(key)

        return jsonify(
            results=res,
            boolean=cache_results["boolean_srv"],
            string=cache_results["string_srv"],
            integer=cache_results["integer_srv"],
            multi=cache_results["multi_srv"],
            pair=cache_results["pair_associations"],
            advanced=cache_results["advanced_type"],
            server_doc=cache_results["doc"],
            suggest=cache_results["values"],
            placeholders=cache_results["placeholders"],
            defaults=cache_results["defaults"],
        )


@ns.route("/clients", "/<server>/clients", endpoint="clients_list")
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
    },
)
class ClientsList(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_clients()
        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")
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
    },
)
class TemplatesList(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_templates()
        return jsonify(result=res)


@ns.route("/template", "/<server>/template", endpoint="new_template", methods=["PUT"])
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
    },
)
class NewTemplateSettings(Resource):
    parser = ns.parser()
    parser.add_argument("newtemplate", required=True, help="No 'newclient' 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()["newtemplate"]
        if not newtemplate:
            self.abort(400, "No template name provided")
        parser = bui.client.get_parser(agent=server)
        templates = parser.list_templates()
        for tpl in templates:
            if tpl["name"] == newtemplate:
                self.abort(409, "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, True, False, server
        )
        if server:
            url = url_for(
                "view.cli_settings", server=server, client=newtemplate, template=True
            )
        else:
            url = url_for("view.cli_settings", client=newtemplate, template=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 template {newtemplate}", server=server)
        return {"notif": noti}, 201


@ns.route("/config", "/<server>/config", endpoint="new_client", methods=["PUT"])
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
    },
)
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"
    )
    @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 client"""
        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")

        if (
            not current_user.is_anonymous
            and current_user.acl.is_moderator()
            and not current_user.acl.is_client_rw(newclient, server)
        ):
            self.abort(403, "You don't have rights on this server")

        parser = bui.client.get_parser(agent=server)
        clients = parser.list_clients()
        for cl in clients:
            if cl["name"] == newclient:
                self.abort(409, "Client '{}' already exists".format(newclient))
        # clientconfdir = bui.client.get_parser_attr('clientconfdir', server)
        # if not clientconfdir:
        #    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])
        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:
            url = url_for("view.cli_settings", client=newclient)
        noti.append(
            [
                NOTIF_INFO,
                _(
                    "<a href=\"%(url)s\">Click here</a> to edit '%(client)s' configuration",
                    url=url,
                    client=newclient,
                ),
            ]
        )
        # clear the cache when we add a new client
        cache.clear()
        # clear client-side cache through the _extra META variable
        try:
            _extra = session.get("_extra", g.now)
            _extra = int(_extra)
        except ValueError:
            _extra = 0
        session["_extra"] = "{}".format(_extra + 1)
        if bui.config["WITH_CELERY"]:
            from ..tasks import force_scheduling_now

            force_scheduling_now()

        bui.audit.logger.info(
            f"created new client configuration {newclient}", server=server
        )
        return {"notif": noti}, 201


@ns.route(
    "/config/<client>",
    "/config/<client>/<path:conf>",
    "/<server>/config/<client>",
    "/<server>/config/<client>/<path:conf>",
    endpoint="client_settings",
    methods=["GET", "POST", "PUT", "DELETE"],
)
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
        "client": "Client name",
        "conf": "Path of the configuration file",
    },
)
class ClientSettings(Resource):
    parser_delete = ns.parser()
    parser_delete.add_argument(
        "revoke",
        type=inputs.boolean,
        help="Whether to revoke the certificate or not",
        default=False,
        nullable=True,
    )
    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")
    )
    @ns.expect(parser_post)
    @ns.doc(
        responses={
            200: "Success",
            403: "Insufficient permissions",
            500: "Internal failure",
        }
    )
    def post(self, server=None, client=None, conf=None):
        """Saves a given client configuration"""
        if (
            not current_user.is_anonymous
            and current_user.acl.is_moderator()
            and not current_user.acl.is_client_rw(client, server)
        ):
            self.abort(403, "You don't have rights on this server")

        args = self.parser_post.parse_args()
        template = args.get("template", False)
        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
        try:
            _extra = session.get("_extra", g.now)
            _extra = int(_extra)
        except ValueError:
            _extra = 0
        session["_extra"] = "{}".format(_extra + 1)

        bui.audit.logger.info(
            f"updated client configuration {client} ({conf})", server=server
        )
        return {"notif": noti}

    @api.acl_admin_or_moderator_required(
        message=_("Sorry, you don't have rights to access the setting panel")
    )
    @ns.expect(parser_get)
    @ns.doc(
        responses={
            200: "Success",
            403: "Insufficient permissions",
            500: "Internal failure",
        }
    )
    def get(self, server=None, client=None, conf=None):
        """Reads a given client configuration"""
        try:
            conf = unquote(conf)
        except:
            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, statictemplate)
        refresh()
        # Translate the doc and placeholder API side
        cache_keys = {
            "doc": "_doc_parser_{}-{}".format(server, g.locale),
            "placeholders": "_placeholders_parser_{}-{}".format(server, g.locale),
            "boolean_cli": "_boolean_cli_parser_{}".format(server),
            "string_cli": "_string_cli_parser_{}".format(server),
            "integer_cli": "_integer_cli_parser_{}".format(server),
            "multi_cli": "_multi_cli_parser_{}".format(server),
            "values": "_suggest_parser_{}".format(server),
            "defaults": "_defaults_parser_{}".format(server),
        }
        cache_results = {}
        for name, key in cache_keys.items():
            if not cache.cache.has(key):
                if name in ["doc", "placeholders"]:
                    _tmp = bui.client.get_parser_attr(name, server).copy()
                    _tmp2 = {}
                    for k, v in _tmp.items():
                        _tmp2[k] = _(v)
                    cache_results[name] = _tmp2
                else:
                    cache_results[name] = bui.client.get_parser_attr(name, server)
                cache.cache.set(key, cache_results[name], 3600)
            else:
                cache_results[name] = cache.cache.get(key)

        return jsonify(
            results=res,
            boolean=cache_results["boolean_cli"],
            string=cache_results["string_cli"],
            integer=cache_results["integer_cli"],
            multi=cache_results["multi_cli"],
            server_doc=cache_results["doc"],
            suggest=cache_results["values"],
            placeholders=cache_results["placeholders"],
            defaults=cache_results["defaults"],
        )

    @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_delete)
    @ns.doc(
        responses={
            200: "Success",
            403: "Insufficient permissions",
            409: "Conflict",
            500: "Internal failure",
        }
    )
    def delete(self, server=None, client=None, conf=None):
        """Deletes a given client"""
        if (
            not current_user.is_anonymous
            and current_user.acl.is_moderator()
            and not current_user.acl.is_client_rw(client, server)
        ):
            self.abort(403, "You don't have rights on this server")

        if bui.client.is_backup_running(client, server):
            self.abort(
                409,
                "There is currently a backup running for this client hence "
                "we cannot delete it for now. Please try again later",
            )

        args = self.parser_delete.parse_args()
        delcert = args.get("delcert", False)
        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:
            # clear the cache when we remove a client
            cache.clear()
            # clear client-side cache through the _extra META variable
            try:
                _extra = session.get("_extra", g.now)
                _extra = int(_extra)
            except ValueError:
                _extra = 0
            session["_extra"] = "{}".format(_extra + 1)
            if bui.config["WITH_CELERY"]:
                from ..tasks import force_scheduling_now

                force_scheduling_now()
        parser = bui.client.get_parser(agent=server)

        bui.audit.logger.info(
            f"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} "
            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")
    )
    @ns.expect(parser_put)
    @ns.doc(
        responses={
            200: "Success",
            403: "Insufficient permissions",
            409: "Conflict",
            500: "Internal failure",
        }
    )
    def put(self, server=None, client=None, conf=None):
        """Rename a given client"""
        if (
            not current_user.is_anonymous
            and current_user.acl.is_moderator()
            and not current_user.acl.is_client_rw(client, server)
        ):
            self.abort(403, "You don't have rights on this server")

        if bui.client.is_backup_running(client, server):
            self.abort(
                409,
                "There is currently a backup running for this client hence "
                "we cannot delete it for now. Please try again later",
            )

        args = self.parser_put.parse_args()
        newname = args.get("newname", None)
        keepcert = args.get("keepcert", False)
        keepdata = args.get("keepdata", False)
        template = args.get("template", False)
        statictemplate = args.get("statictemplate", False)

        # clear the cache when we remove a client
        cache.clear()
        # clear client-side cache through the _extra META variable
        try:
            _extra = session.get("_extra", g.now)
            _extra = int(_extra)
        except ValueError:
            _extra = 0
        session["_extra"] = "{}".format(_extra + 1)
        if bui.config["WITH_CELERY"]:
            from ..tasks import force_scheduling_now

            force_scheduling_now()
        parser = bui.client.get_parser(agent=server)

        bui.audit.logger.info(
            f"renaming client configuration {client} to {newname}, "
            f"keep data: {keepdata}, keep certificate: {keepcert}, "
            f"is template: {template}, is static template: {statictemplate}",
            server=server,
        )
        return (
            parser.rename_client(
                client, newname, template, statictemplate, keepcert, keepdata
            ),
            200,
        )


@ns.route(
    "/path-expander",
    "/<server>/path-expander",
    "/path-expander/<client>",
    "/<server>/path-expander/<client>",
    endpoint="path_expander",
)
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
        "client": "Client name",
    },
)
class PathExpander(Resource):

    parser = ns.parser()
    parser.add_argument("path", required=True, help="No 'path' provided")
    parser.add_argument("source", required=False, help="Which file is it included in")

    @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, client=None):
        """Expends a given path

        For instance if it's given a glob expression it will returns a list of
        files matching the expression.
        """
        args = self.parser.parse_args()
        path = args["path"]
        source = args["source"]
        if path:
            path = unquote(path)
        if source:
            source = unquote(source)
        parser = bui.client.get_parser(agent=server)
        paths = parser.path_expander(path, source, client)
        if not paths:
            self.abort(403, "Path not found")
        return {"result": paths}


@ns.route("/options", "/<server>/options", endpoint="setting_options")
@ns.doc(
    params={
        "server": "Which server to collect data from when in multi-agent mode",
    },
)
class SettingOptions(Resource):
    @ns.doc(
        responses={
            200: "Success",
            403: "Insufficient permissions",
            500: "Internal failure",
        }
    )
    def get(self, server=None):
        """Returns various setting options"""
        return {
            "is_revocation_enabled": bui.client.revocation_enabled(server),
            "server_can_restore": not bui.noserverrestore
            or bui.client.get_parser(agent=server).param(
                "server_can_restore", "client_conf"
            ),
            "batch_list_supported": bui.client.get_attr(
                "batch_list_supported", False, server
            ),
        }
