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

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

"""
from . import api
from ..engines.server import BUIServer  # noqa
from ..sessions import session_manager
from ..misc.acl.meta import meta_grants
from ..utils import NOTIF_OK
from .custom import fields, Resource

#  from ..exceptions import BUIserverException

from flask import current_app
from flask_login import current_user
from flask_babel import gettext

import json

bui = current_app  # type: BUIServer
ns = api.namespace("admin", "Admin methods")

user_fields = ns.model(
    "Users",
    {
        "id": fields.String(required=True, description="User id"),
        "name": fields.String(required=True, description="User name"),
        "backend": fields.String(required=True, description="Backend name"),
    },
)
grant_fields = ns.model(
    "Grants",
    {
        "id": fields.String(required=True, description="Grant id"),
        "grant": fields.String(required=True, description="Grant content"),
        "backend": fields.String(required=True, description="Backend name"),
    },
)
group_fields = ns.model(
    "Groups",
    {
        "id": fields.String(required=True, description="Group id"),
        "grant": fields.String(required=True, description="Group grant content"),
        "members": fields.List(
            fields.String, required=True, description="Group members"
        ),
        "backend": fields.String(required=True, description="Backend name"),
    },
)
groups_fields = ns.model(
    "GroupsFields",
    {
        "name": fields.String(required=True, description="Group name"),
        "inherit": fields.List(
            fields.String, required=False, description="This group is inherited by"
        ),
    },
)
groups_list_fields = ns.model(
    "GroupsList",
    {
        "groups": fields.List(
            fields.Nested(groups_fields), required=True, description="Groups list"
        ),
    },
)
group_members_fields = ns.model(
    "GroupMembers",
    {
        "members": fields.List(
            fields.String, required=True, description="Group members"
        ),
        "grant": fields.String(required=True, description="Group grant content"),
    },
)
is_moderator_fields = ns.model(
    "IsModerator",
    {
        "moderator": fields.Boolean(
            required=True, description="Is the member a moderator"
        ),
        "inherit": fields.List(
            fields.String,
            required=False,
            description="What provides this grant if inherited",
        ),
    },
)
moderator_members_fields = ns.model(
    "ModeratorMembers",
    {
        "members": fields.List(
            fields.String, required=True, description="Moderator members"
        ),
        "grant": fields.String(required=True, description="Moderator grant content"),
    },
)
moderators_fields = ns.model(
    "Moderators",
    {
        "members": fields.List(
            fields.String, required=True, description="Moderator members"
        ),
        "grant": fields.String(required=True, description="Moderator grant content"),
        "backend": fields.String(required=True, description="Backend name"),
    },
)
is_admin_fields = ns.model(
    "IsAdmin",
    {
        "admin": fields.Boolean(required=True, description="Is the member an admin"),
        "inherit": fields.List(
            fields.String,
            required=False,
            description="What provides this grant if inherited",
        ),
    },
)
admin_members_fields = ns.model(
    "AdminMembers",
    {
        "members": fields.List(
            fields.String, required=True, description="Admin members"
        ),
    },
)
admins_fields = ns.model(
    "Admins",
    {
        "members": fields.List(
            fields.String, required=True, description="Admin members"
        ),
        "backend": fields.String(required=True, description="Backend name"),
    },
)
session_fields = ns.model(
    "Sessions",
    {
        "uuid": fields.String(description="Session id"),
        "ip": fields.String(description="IP address"),
        "ua": fields.String(description="User-Agent"),
        "permanent": fields.Boolean(description="Remember cookie"),
        "api": fields.Boolean(description="API login"),
        "expire": fields.DateTime(description="Expiration date"),
        "timestamp": fields.DateTime(description="Last seen"),
        "current": fields.Boolean(description="Is current session", default=False),
    },
)
acl_backend_fields = ns.model(
    "AclBackends",
    {
        "name": fields.String(required=True, description="Backend name"),
        "description": fields.String(required=True, description="Backend description"),
        "type": fields.String(required=False, description="Backend type"),
        "priority": fields.Integer(required=False, description="Backend priority"),
        "add_grant": fields.Boolean(
            required=False, default=False, description="Support grant creation"
        ),
        "mod_grant": fields.Boolean(
            required=False, default=False, description="Support grant edition"
        ),
        "del_grant": fields.Boolean(
            required=False, default=False, description="Support grant deletion"
        ),
        "add_group": fields.Boolean(
            required=False, default=False, description="Support group creation"
        ),
        "mod_group": fields.Boolean(
            required=False, default=False, description="Support group edition"
        ),
        "del_group": fields.Boolean(
            required=False, default=False, description="Support group deletion"
        ),
        "add_group_member": fields.Boolean(
            required=False, default=False, description="Support group member addition"
        ),
        "del_group_member": fields.Boolean(
            required=False, default=False, description="Support group member deletion"
        ),
        "add_moderator": fields.Boolean(
            required=False, default=False, description="Support moderator creation"
        ),
        "mod_moderator": fields.Boolean(
            required=False, default=False, description="Support moderator edition"
        ),
        "del_moderator": fields.Boolean(
            required=False, default=False, description="Support moderator deletion"
        ),
        "add_admin": fields.Boolean(
            required=False, default=False, description="Support admin creation"
        ),
        "del_admin": fields.Boolean(
            required=False, default=False, description="Support admin deletion"
        ),
    },
)
auth_backend_fields = ns.model(
    "Backends",
    {
        "name": fields.String(required=True, description="Backend name"),
        "description": fields.String(required=True, description="Backend description"),
        "type": fields.String(required=False, description="Backend type"),
        "priority": fields.Integer(required=False, description="Backend priority"),
        "add": fields.Boolean(
            required=False, default=False, description="Support user creation"
        ),
        "mod": fields.Boolean(
            required=False, default=False, description="Support user edition"
        ),
        "del": fields.Boolean(
            required=False, default=False, description="Support user deletion"
        ),
    },
)


@ns.route("/me", endpoint="admin_me")
class AdminMe(Resource):
    """The :class:`burpui.api.admin.AdminMe` resource allows you to
    retrieve informations about your currently logged in user.

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

    @ns.marshal_with(user_fields)
    def get(self):
        """Returns the current user informations

        **GET** method provided by the webservice.

        :returns: User
        """
        ret = getattr(current_user, "real", current_user)
        return ret


@ns.route(
    "/acl/isAdmin/<member>", "/acl/<backend>/isAdmin/<member>", endpoint="acl_is_admin"
)
@ns.doc(
    params={
        "member": "Username",
    }
)
class AclIsAdmin(Resource):
    """The :class:`burpui.api.admin.AclIsAdmin` resources allows you to check
    if a given member is admin or not.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view admins list")
    @ns.marshal_with(is_admin_fields)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, member, backend=None):
        """Checks if a given member is admin"""
        if not backend:
            (ret, inh) = meta_grants.is_admin(member)
            return {"admin": ret, "inherit": inh}
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))
        loader = handler.backends[backend]
        return {"admin": member in loader.admins}


@ns.route("/acl/admins", "/acl/<backend>/admins", endpoint="acl_admins")
@ns.doc(
    params={
        "backend": "Backend name",
    }
)
class AclAdminss(Resource):
    """The :class:`burpui.api.admin.AclAdminss` resource allows you to
    retrieve a list of admins.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view groups list")
    @ns.marshal_list_with(admins_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, name=None, backend=None):
        """Returns a list of admins

        **GET** method provided by the webservice.

        :returns: Moderators
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        ret = []
        for _, loader in handler.backends.items():
            append = {"members": loader.admins, "backend": loader.name}
            if (backend and backend == append["backend"]) or backend is None:
                return [append]
            ret.append(append)
        return ret


@ns.route(
    "/acl/admin",
    "/acl/<backend>/admin",
    "/acl/<backend>/admin/<member>",
    endpoint="acl_admin",
)
@ns.doc(
    params={
        "backend": "ACL backend",
        "member": "Admin member",
    }
)
class AclAdmin(Resource):
    """The :class:`burpui.api.admin.AclAdmins` resource allows you to
    retrieve a list of admins and add/delete them if your
    acl backend support those actions.

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

    parser = ns.parser()
    parser.add_argument(
        "memberNames", required=False, help="Admin members", action="append"
    )
    parser.add_argument("backendName", required=False, help="Backend name")

    @api.acl_admin_or_moderator_required(message="Not allowed to view admins list")
    @ns.marshal_with(admin_members_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, backend):
        """Returns a list of admin users

        **GET** method provided by the webservice.

        :returns: Members
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))
        ret = {}
        loader = handler.backends[backend]
        ret = {"members": loader.admins}
        return ret

    @api.disabled_on_demo()
    @api.acl_admin_required(message="Not allowed to add admin members")
    @ns.expect(parser)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def put(self, backend=None, member=None):
        """Add a member as admin

        **PUT** method provided by the webservice.
        """
        args = self.parser.parse_args()
        backend = backend or args["backendName"]
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        members = [member] if member else (args["memberNames"] or [])

        if loader.add_admin is False:
            self.abort(
                500,
                "The '{}' backend does not support moderator member addition"
                "".format(backend),
            )

        ret = []
        status = 200

        for member in members:
            success, message, code = loader.add_admin(member)
            status = 201 if success else 200
            ret.append([code, message])

        bui.audit.logger.info(f"granted {members} as admin")
        return ret, status

    @api.disabled_on_demo()
    @api.acl_admin_required(message="Not allowed to remove admin members")
    @ns.expect(parser)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def delete(self, backend=None, member=None):
        """Remove an admin member

        **DELETE** method provided by the webservice.
        """
        args = self.parser.parse_args()
        backend = backend or args["backendName"]
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(40422, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        members = [member] if member else (args["memberNames"] or [])

        if loader.del_admin is False:
            self.abort(
                500,
                "The '{}' backend does not support admin member deletion"
                "".format(backend),
            )

        ret = []
        status = 200

        for member in members:
            success, message, code = loader.del_admin(member)
            status = 201 if success else 200
            ret.append([code, message])

        bui.audit.logger.info(f"removed admin grants of {members}")
        return ret, status


@ns.route(
    "/acl/isModerator/<member>",
    "/acl/<backend>/isModerator/<member>",
    endpoint="acl_is_moderator",
)
@ns.doc(
    params={
        "member": "Username",
    }
)
class AclIsModerator(Resource):
    """The :class:`burpui.api.admin.AclIsModerator` resources allows you to check
    if a given member is moderator or not.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view admins list")
    @ns.marshal_with(is_moderator_fields)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, member, backend=None):
        """Checks if a given member is moderator"""
        if not backend:
            (ret, inh) = meta_grants.is_moderator(member)
            return {"moderator": ret, "inherit": inh}
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))
        loader = handler.backends[backend]
        return {"moderator": member in loader.moderators}


@ns.route("/acl/moderators", "/acl/<backend>/moderators", endpoint="acl_moderators")
@ns.doc(
    params={
        "backend": "Backend name",
    }
)
class AclModerators(Resource):
    """The :class:`burpui.api.admin.AclModerators` resource allows you to
    retrieve a list of moderators.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view groups list")
    @ns.marshal_list_with(moderators_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, name=None, backend=None):
        """Returns a list of moderators

        **GET** method provided by the webservice.

        :returns: Moderators
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        ret = []
        for _, loader in handler.backends.items():
            append = {
                "grant": loader.moderator,
                "members": loader.moderators,
                "backend": loader.name,
            }
            if (backend and backend == append["backend"]) or backend is None:
                return [append]
            ret.append(append)
        return ret


@ns.route(
    "/acl/moderator",
    "/acl/<backend>/moderator",
    "/acl/<backend>/moderator/<member>",
    endpoint="acl_moderator",
)
@ns.doc(
    params={
        "backend": "ACL backend",
        "member": "Moderator member",
    }
)
class AclModerator(Resource):
    """The :class:`burpui.api.admin.AclModerator` resource allows you to
    retrieve a list of moderators and add/delete them if your
    acl backend support those actions.

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

    parser = ns.parser()
    parser.add_argument(
        "memberNames", required=False, help="Moderator members", action="append"
    )
    parser.add_argument("backendName", required=False, help="Backend name")

    parser_mod = ns.parser()
    parser_mod.add_argument("grant", required=False, help="Moderator grants")

    @api.acl_admin_or_moderator_required(message="Not allowed to view moderators list")
    @ns.marshal_with(moderator_members_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, backend):
        """Returns a list of moderator users

        **GET** method provided by the webservice.

        :returns: Members
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))
        ret = {}
        loader = handler.backends[backend]
        ret = {"members": loader.moderators, "grant": loader.moderator}
        return ret

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to add moderator members")
    @ns.expect(parser)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def put(self, backend=None, member=None):
        """Add a member as moderator

        **PUT** method provided by the webservice.
        """
        args = self.parser.parse_args()
        backend = backend or args["backendName"]
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        members = [member] if member else (args["memberNames"] or [])

        if loader.add_moderator is False:
            self.abort(
                500,
                "The '{}' backend does not support moderator member addition"
                "".format(backend),
            )

        ret = []
        status = 200
        for member in members:
            success, message, code = loader.add_moderator(member)
            ret.append([code, message])
            status = 201 if success else 200

        bui.audit.logger.info(f"granted {members} as moderator")
        return ret, status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(
        message="Not allowed to remove moderator members"
    )
    @ns.expect(parser)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def delete(self, backend=None, member=None):
        """Remove a moderator member

        **DELETE** method provided by the webservice.
        """
        args = self.parser.parse_args()
        backend = backend or args["backendName"]
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        members = [member] if member else (args["memberNames"] or [])

        if loader.del_moderator is False:
            self.abort(
                500,
                "The '{}' backend does not support moderator member deletion"
                "".format(backend),
            )

        ret = []
        status = 200
        for member in members:
            success, message, code = loader.del_moderator(member)
            ret.append([code, message])
            status = 201 if success else 200

        bui.audit.logger.info(f"removed moderator grant of {members}")
        return ret, status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(
        message="Not allowed to update moderator grants"
    )
    @ns.expect(parser_mod)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def post(self, backend):
        """Update moderator grants

        **POST** method provided by the webservice.
        """
        args = self.parser_mod.parse_args()
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        grants = args["grant"]

        if loader.mod_moderator is False:
            self.abort(
                500,
                "The '{}' backend does not support moderator grants edition"
                "".format(backend),
            )

        success, message, code = loader.mod_moderator(grants)
        status = 201 if success else 200

        bui.audit.logger.info(f"updated moderator grants to: {grants}")
        return [[code, message]], status


@ns.route(
    "/acl/group",
    "/acl/<backend>/group/<name>",
    "/acl/<backend>/group/<name>/<member>",
    endpoint="acl_group_members",
)
@ns.doc(
    params={
        "name": "Group name",
        "backend": "ACL backend",
        "member": "Group member",
    }
)
class AclGroup(Resource):
    """The :class:`burpui.api.admin.AclGroup` resource allows you to
    retrieve a list of members in a given group and add/delete them if your
    acl backend support those actions.

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

    parser = ns.parser()
    parser.add_argument(
        "memberNames", required=False, help="Group members", action="append"
    )
    parser.add_argument("groupName", required=False, help="Group name")
    parser.add_argument("backendName", required=False, help="Backend name")

    @api.acl_admin_or_moderator_required(message="Not allowed to view groups list")
    @ns.marshal_with(group_members_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, name, backend):
        """Returns a list of users in a giver group

        **GET** method provided by the webservice.

        :returns: Members
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))
        ret = {}
        loader = handler.backends[backend]
        groups = loader.groups
        gname = "@{}".format(name)
        if groups and gname in groups:
            ret = {
                "members": groups[gname].get("members", []),
                "grant": groups[gname].get("grants", ""),
            }
        return ret

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to add member in group")
    @ns.expect(parser)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def put(self, name=None, backend=None, member=None):
        """Add a member in a given group

        **PUT** method provided by the webservice.
        """
        args = self.parser.parse_args()
        name = name or args["groupName"]
        backend = backend or args["backendName"]
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        members = [member] if member else (args["memberNames"] or [])

        if loader.add_group_member is False:
            self.abort(
                500,
                "The '{}' backend does not support group member addition"
                "".format(backend),
            )

        ret = []
        status = 200
        for member in members:
            success, message, code = loader.add_group_member(name, member)
            ret.append([code, message])
            status = 201 if success else 200

        bui.audit.logger.info(f"added {members} in group {name}")
        return ret, status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(
        message="Not allowed to remove member in group"
    )
    @ns.expect(parser)
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def delete(self, name, backend, member=None):
        """Remove a member from a given group

        **DELETE** method provided by the webservice.
        """
        args = self.parser.parse_args()
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        if backend not in handler.backends:
            self.abort(404, "No acl backend '{}' found".format(backend))

        loader = handler.backends[backend]

        members = [member] if member else (args["memberNames"] or [])

        if loader.del_group_member is False:
            self.abort(
                500,
                "The '{}' backend does not support group member deletion"
                "".format(backend),
            )

        ret = []
        status = 200
        for member in members:
            success, message, code = loader.del_group_member(name, member)
            ret.append([code, message])
            status = 201 if success else 200

        bui.audit.logger.info(f"removed {members} from group {name}")
        return ret, status


@ns.route("/acl/groupsOf/<member>", endpoint="acl_groups_of")
@ns.doc(
    params={
        "member": "Username",
    }
)
class AclGroupsOf(Resource):
    """The :class:`burpui.api.admin.AclGroupsOf` resource allows you to retrieve
    a list of groups of a given user.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view groups list")
    @ns.marshal_with(groups_list_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, member):
        """Returns a list of group

        **GET** method provided by the webservice.

        :returns: Groups
        """
        return {
            "groups": [
                {"name": name, "inherit": inherit}
                for name, inherit in meta_grants.get_member_groups(member)
            ]
        }


@ns.route(
    "/acl/groups",
    "/acl/<backend>/groups",
    "/acl/groups/<name>",
    "/acl/<backend>/groups/<name>",
    endpoint="acl_groups",
)
@ns.doc(
    params={
        "name": "Group name",
        "backend": "Backend name",
    }
)
class AclGroups(Resource):
    """The :class:`burpui.api.admin.AclGroups` resource allows you to
    retrieve a list of groups and to add/update/delete them if your
    acl backend support those actions.

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

    parser_add = ns.parser()
    parser_add.add_argument(
        "group", required=True, help="Group name", location="values"
    )
    parser_add.add_argument("grant", required=True, help="Group grant content")
    parser_add.add_argument("backend", help="Backend", location="values")

    parser_mod = ns.parser()
    parser_mod.add_argument("grant", required=True, help="Group grant content")
    parser_mod.add_argument("backend", help="Backend", location="values")

    parser_del = ns.parser()
    parser_del.add_argument("backend", help="Backend", location="values")

    @api.acl_admin_or_moderator_required(message="Not allowed to view groups list")
    @ns.marshal_list_with(group_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, name=None, backend=None):
        """Returns a list of group

        **GET** method provided by the webservice.

        :returns: Groups
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        ret = []
        for _, loader in handler.backends.items():
            groups = loader.groups
            if groups:
                for _id, group in groups.items():
                    append = {
                        "id": _id.lstrip("@"),
                        "grant": group.get("grants", ""),
                        "members": group.get("members", []),
                        "backend": loader.name,
                    }
                    if name and name == append["id"]:
                        if (
                            backend and backend == append["backend"]
                        ) or backend is None:
                            return [append]
                    ret.append(append)
        return ret

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to create groups")
    @ns.expect(parser_add)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def put(self, backend=None):
        """Create a new group"""
        args = self.parser_add.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No acl backend found")

        loader = handler.backends[backend]

        if loader.add_group is False:
            self.abort(
                500,
                "The '{}' backend does not support group creation" "".format(backend),
            )

        success, message, code = loader.add_group(args["group"], args["grant"])
        status = 201 if success else 200

        bui.audit.logger.info(
            f'created new group {args["group"]} with grants: {args["grant"]}'
        )
        return [[code, message]], status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to delete this group")
    @ns.expect(parser_del)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def delete(self, name, backend=None):
        """Delete a group"""
        args = self.parser_del.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No acl backend found")

        loader = handler.backends[backend]

        if loader.del_group is False:
            self.abort(
                500,
                "The '{}' backend does not support group deletion" "".format(backend),
            )

        success, message, code = loader.del_group(name)
        status = 201 if success else 200

        bui.audit.logger.info(f"removed group {name}")
        return [[code, message]], status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to modify this group")
    @ns.expect(parser_mod)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def post(self, name, backend=None):
        """Change a group"""
        args = self.parser_mod.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No acl backend found")

        loader = handler.backends[backend]

        if loader.mod_group is False:
            self.abort(
                500,
                "The '{}' backend does not support group modification"
                "".format(backend),
            )

        success, message, code = loader.mod_group(name, args["grant"])
        status = 201 if success else 200

        bui.audit.logger.info(f'updated group {name} with: {args["grant"]}')
        return [[code, message]], status


@ns.route(
    "/acl/grants",
    "/acl/<backend>/grants",
    "/acl/grants/<name>",
    "/acl/<backend>/grants/<name>",
    endpoint="acl_grants",
)
@ns.doc(
    params={
        "name": "Grant name",
        "backend": "Backend name",
    }
)
class AclGrants(Resource):
    """The :class:`burpui.api.admin.AclGrants` resource allows you to
    retrieve a list of grants and to add/update/delete them if your
    acl backend support those actions.

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

    parser_add = ns.parser()
    parser_add.add_argument("grant", required=True, help="Grant name")
    parser_add.add_argument("content", required=True, help="Grant content")
    parser_add.add_argument("backend", help="Backend")

    parser_mod = ns.parser()
    parser_mod.add_argument("content", required=True, help="Grant content")
    parser_mod.add_argument("backend", help="Backend")

    parser_del = ns.parser()
    parser_del.add_argument("backend", help="Backend", location="values")

    @api.acl_admin_or_moderator_required(message="Not allowed to view grants list")
    @ns.marshal_list_with(grant_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, name=None, backend=None):
        """Returns a list of grants

        **GET** method provided by the webservice.

        :returns: Grants
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No acl backend found")
        ret = []
        for _, loader in handler.backends.items():
            grants = loader.grants
            if grants:
                for _id, grant in grants.items():
                    append = {
                        "id": _id,
                        "grant": json.dumps(grant),
                        "backend": loader.name,
                    }
                    if name and name == _id:
                        if (backend and backend == loader.name) or backend is None:
                            return [append]
                    ret.append(append)
        return ret

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to create grants")
    @ns.expect(parser_add)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def put(self, backend=None):
        """Create a new grant"""
        args = self.parser_add.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No acl backend found")

        loader = handler.backends[backend]

        if loader.add_grant is False:
            self.abort(
                500,
                "The '{}' backend does not support grant creation" "".format(backend),
            )

        success, message, code = loader.add_grant(args["grant"], args["content"])
        status = 201 if success else 200

        bui.audit.logger.info(f'added grant {args["grant"]} with: {args["content"]}')
        return [[code, message]], status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to delete this grant")
    @ns.expect(parser_del)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def delete(self, name, backend=None):
        """Delete a grant"""
        args = self.parser_del.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No acl backend found")

        loader = handler.backends[backend]

        if loader.del_grant is False:
            self.abort(
                500,
                "The '{}' backend does not support grant deletion" "".format(backend),
            )

        success, message, code = loader.del_grant(name)
        status = 201 if success else 200

        bui.audit.logger.info(f"removed grant {name}")
        return [[code, message]], status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to modify this grant")
    @ns.expect(parser_mod)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def post(self, name, backend=None):
        """Change a grant"""
        args = self.parser_mod.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No acl backend found")

        loader = handler.backends[backend]

        if loader.mod_grant is False:
            self.abort(
                500,
                "The '{}' backend does not support grant modification"
                "".format(backend),
            )

        success, message, code = loader.mod_grant(name, args["content"])
        status = 201 if success else 200

        bui.audit.logger.info(f'updated grant {name} with: {args["content"]}')
        return [[code, message]], status


@ns.route("/acl/backend/<backend>", endpoint="acl_backend")
class AclBackend(Resource):
    """The :class:`burpui.api.admin.AclBackend` resource allows you to
    retrieve a given ACL backend with its capabilities.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view backends list")
    @ns.marshal_with(acl_backend_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, backend):
        """Returns a given ACL backend

        **GET** method provided by the webservice.

        :returns: Backend
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No authentication backend found")
        if backend not in handler.backends:
            self.abort(404, "ACL backend {} not found".format(backend))
        loader = handler.backends[backend]
        back = {}
        back["name"] = backend
        back["description"] = gettext(loader.__doc__)
        back["type"] = "authorization"
        back["priority"] = getattr(loader, "priority", -1)
        for method in [
            "add_grant",
            "del_grant",
            "mod_grant",
            "add_group",
            "del_group",
            "mod_group",
            "add_group_member",
            "del_group_member",
            "add_moderator",
            "del_moderator",
            "mod_moderator",
            "add_admin",
            "del_admin",
        ]:
            back[method] = getattr(loader, method, False) is not False

        return back


@ns.route("/acl/backends", endpoint="acl_backends")
class AclBackends(Resource):
    """The :class:`burpui.api.admin.AclBackends` resource allows you to
    retrieve a list of ACL backends with their capabilities.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view backends list")
    @ns.marshal_list_with(acl_backend_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self):
        """Returns a list of backends

        **GET** method provided by the webservice.

        :returns: Backends
        """
        try:
            handler = getattr(bui, "acl_handler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No authentication backend found")
        ret = []
        for name, backend in handler.backends.items():
            back = {}
            back["name"] = name
            back["description"] = gettext(backend.__doc__)
            back["type"] = "authorization"
            back["priority"] = getattr(backend, "priority", -1)
            for method in [
                "add_grant",
                "del_grant",
                "mod_grant",
                "add_group",
                "del_group",
                "mod_group",
                "add_group_member",
                "del_group_member",
                "add_moderator",
                "del_moderator",
                "mod_moderator",
                "add_admin",
                "del_admin",
            ]:
                back[method] = getattr(backend, method, False) is not False
            ret.append(back)

        return ret


@ns.route(
    "/auth/users",
    "/auth/<backend>/users",
    "/auth/users/<name>",
    "/auth/<backend>/users/<name>",
    endpoint="auth_users",
)
@ns.doc(
    params={
        "name": "Username",
        "backend": "Authentication backend",
    }
)
class AuthUsers(Resource):
    """The :class:`burpui.api.admin.AuthUsers` resource allows you to
    retrieve a list of users and to add/update/delete them if your
    authentication backend support those actions.

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

    parser_add = ns.parser()
    parser_add.add_argument("username", required=True, help="Username")
    parser_add.add_argument("password", required=True, help="Password")
    parser_add.add_argument("backend", help="Backend")

    parser_mod = ns.parser()
    parser_mod.add_argument("password", required=True, help="Password")
    parser_mod.add_argument("backend", help="Backend")
    parser_mod.add_argument("old_password", required=False, help="Old password")

    parser_del = ns.parser()
    parser_del.add_argument("backend", help="Backend")

    @api.acl_admin_or_moderator_required(message="Not allowed to view users list")
    @ns.marshal_list_with(user_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, name=None, backend=None):
        """Returns a list of users

        **GET** method provided by the webservice.

        :returns: Users
        """
        try:
            handler = getattr(bui, "uhandler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No authentication backend found")
        ret = []
        for _, backend in handler.backends.items():
            loader = backend.loader
            preload_users = getattr(backend, "preload_users", True)
            try:
                users = getattr(loader, "users")
            except AttributeError:
                continue
            if users:
                if isinstance(users, list):
                    for user in users:
                        append = {
                            "id": backend.user(user).get_id()
                            if preload_users
                            else user,
                            "name": user,
                            "backend": backend.name,
                        }
                        if name and name == append["id"]:
                            if (
                                backend and backend == append["backend"]
                            ) or backend is None:
                                return append
                        ret.append(append)
                elif isinstance(users, dict):
                    for user, _ in users.items():
                        append = {
                            "id": backend.user(user).get_id(),
                            "name": user,
                            "backend": backend.name,
                        }
                        if name and name == append["id"]:
                            if (
                                backend and backend == append["backend"]
                            ) or backend is None:
                                return append
                        ret.append(append)
        return ret

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to create users")
    @ns.expect(parser_add)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def put(self, name=None, backend=None):
        """Create a new user"""
        args = self.parser_add.parse_args()
        username = name or args["username"]
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "uhandler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No authentication backend found")

        backend = handler.backends[backend]

        if backend.add_user is False:
            self.abort(
                500,
                "The '{}' backend does not support user creation" "".format(backend),
            )

        success, message, code = backend.add_user(username, args["password"])
        status = 201 if success else 200

        bui.audit.logger.info(f"created new user: {username}")
        return [[code, message]], status

    @api.disabled_on_demo()
    @api.acl_admin_or_moderator_required(message="Not allowed to delete this user")
    @ns.expect(parser_del)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def delete(self, name, backend=None):
        """Delete a user"""
        args = self.parser_del.parse_args()
        backend = backend or args["backend"]

        try:
            handler = getattr(bui, "uhandler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No authentication backend found")

        backend = handler.backends[backend]

        if backend.del_user is False:
            self.abort(
                500,
                "The '{}' backend does not support user deletion" "".format(backend),
            )

        success, message, code = backend.del_user(name)
        status = 201 if success else 200

        bui.audit.logger.info(f"removed user: {name}")
        return [[code, message]], status

    @api.disabled_on_demo()
    @api.acl_own_or_admin_or_moderator(
        key="name", message="Not allowed to modify this user"
    )
    @ns.expect(parser_mod)
    @ns.doc(
        responses={
            200: "Request performed with errors",
            201: "Success",
            403: "Not allowed",
            400: "Missing parameters",
            404: "Backend not found",
            500: "Backend does not support this operation",
        },
    )
    def post(self, name, backend=None):
        """Change user password"""
        args = self.parser_mod.parse_args()
        backend = backend or args["backend"]
        is_moderator = True

        if not current_user.is_anonymous:
            is_moderator = (
                current_user.acl.is_admin() or current_user.acl.is_moderator()
            )

        if not is_moderator and not args["old_password"]:
            self.abort(400, "Old password required")

        try:
            handler = getattr(bui, "uhandler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0 or backend not in handler.backends:
            self.abort(404, "No authentication backend found")

        backend = handler.backends[backend]

        if backend.change_password is False:
            self.abort(
                500,
                "The '{}' backend does not support user modification"
                "".format(backend),
            )

        success, message, code = backend.change_password(
            name, args["password"], args.get("old_password")
        )
        status = 201 if success else 200

        bui.audit.logger.info(f"changed password of user {name}")
        return [[code, message]], status


@ns.route("/auth/backend/<backend>", endpoint="auth_backend")
class AuthBackend(Resource):
    """The :class:`burpui.api.admin.AuthBackend` resource allows you to
    retrieve a given authentication backend and its capabilities.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view backends list")
    @ns.marshal_with(auth_backend_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self, backend):
        """Returns a given authentication backend

        **GET** method provided by the webservice.

        :returns: Backend
        """
        try:
            handler = getattr(bui, "uhandler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No authentication backend found")
        if backend not in handler.backends:
            self.abort(404, "No authentication backend {} found".format(backend))
        back = handler.backends[backend]
        ret = {
            "name": backend,
            "description": gettext(back.__doc__),
            "type": "authentication",
            "priority": getattr(back, "priority", -1),
            "add": getattr(back, "add_user", False) is not False,
            "del": getattr(back, "del_user", False) is not False,
            "mod": getattr(back, "change_password", False) is not False,
        }

        return ret


@ns.route("/auth/backends", endpoint="auth_backends")
class AuthBackends(Resource):
    """The :class:`burpui.api.admin.AuthBackends` resource allows you to
    retrieve a list of backends and to add/update/delete users if your
    authentication backend support those actions.

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

    @api.acl_admin_or_moderator_required(message="Not allowed to view backends list")
    @ns.marshal_list_with(auth_backend_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "No backend found",
        },
    )
    def get(self):
        """Returns a list of backends

        **GET** method provided by the webservice.

        :returns: Backends
        """
        try:
            handler = getattr(bui, "uhandler")
        except AttributeError:
            handler = None

        if not handler or len(handler.backends) == 0:
            self.abort(404, "No authentication backend found")
        ret = []
        for name, backend in handler.backends.items():
            ret.append(
                {
                    "name": name,
                    "description": gettext(backend.__doc__),
                    "type": "authentication",
                    "priority": backend.priority,
                    "add": backend.add_user is not False,
                    "del": backend.del_user is not False,
                    "mod": backend.change_password is not False,
                }
            )

        return ret


@ns.route("/session/<user>", "/session/<user>/<uuid:id>", endpoint="other_sessions")
@ns.doc(
    params={
        "user": "User to get sessions from",
        "id": "Session id",
    }
)
class OtherSessions(Resource):
    """The :class:`burpui.api.admin.OtherSessions` resource allows you to
    retrieve a list of sessions for a given user.

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

    @ns.marshal_list_with(session_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "User not found",
        },
    )
    def get(self, user=None, id=None):
        """Returns a list of sessions

        **GET** method provided by the webservice.

        :returns: Sessions
        """
        if id:
            return session_manager.get_session_by_id(str(id))
        if not user:
            self.abort(404, "User not found")
        return session_manager.get_user_sessions(user)


@ns.route("/me/session", "/me/session/<uuid:id>", endpoint="user_sessions")
@ns.doc(
    params={
        "id": "Session id",
    }
)
class MySessions(Resource):
    """The :class:`burpui.api.admin.MySessions` resource allows you to
    retrieve a list of sessions and invalidate them for the current user.

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

    @ns.marshal_list_with(session_fields, code=200, description="Success")
    @ns.doc(
        responses={
            403: "Insufficient permissions",
            404: "User not found",
        },
    )
    def get(self, id=None):
        """Returns a list of sessions

        **GET** method provided by the webservice.

        :returns: Sessions
        """
        if id:
            return session_manager.get_session_by_id(str(id))
        user = getattr(current_user, "name", None)
        if not user:
            self.abort(404, "User not found")
        return session_manager.get_user_sessions(user)

    @api.disabled_on_demo()
    @ns.doc(
        responses={
            201: "Success",
            403: "Insufficient permissions",
            404: "User or session not found",
            400: "Wrong request",
        }
    )
    def delete(self, id=None):
        """Delete a given session

        Note: ``id`` is mandatory
        """
        if not id:
            self.abort(400, "Missing id")
        user = getattr(current_user, "name", None)
        if not user:
            self.abort(404, "User not found")
        store = session_manager.get_session_by_id(str(id))
        if not store:
            self.abort("Session not found")
        if store.user != user:
            if (
                not current_user.is_anonymous
                and not current_user.acl.is_admin()
                and not current_user.acl.is_moderator()
            ):
                self.abort(403, "Insufficient permissions")
            if current_user.acl.is_moderator() and meta_grants.is_admin(store.user):
                self.abort(403, "Insufficient permissions")
        if session_manager.invalidate_session_by_id(store.uuid):
            session_manager.delete_session_by_id(store.uuid)

        bui.audit.logger.info(f"removed session {store.id} of {store.user}")
        return [NOTIF_OK, "Session {} successfully revoked".format(id)], 201
