mirror of
https://github.com/ziirish/burp-ui.git
synced 2026-05-15 06:05:58 -06:00
340 lines
11 KiB
Python
340 lines
11 KiB
Python
# -*- coding: utf8 -*-
|
|
import os
|
|
from collections import OrderedDict
|
|
from importlib import import_module
|
|
|
|
from flask import session
|
|
from flask_login import AnonymousUserMixin
|
|
|
|
from ...sessions import session_manager
|
|
from ...utils import is_uuid
|
|
from ..acl.interface import BUIacl
|
|
from .interface import BUIhandler, BUIuser
|
|
|
|
ACL_METHODS = BUIacl.__abstractmethods__
|
|
|
|
|
|
class UserAuthHandler(BUIhandler):
|
|
"""See :class:`burpui.misc.auth.interface.BUIhandler`"""
|
|
|
|
def __init__(self, app=None):
|
|
"""See :func:`burpui.misc.auth.interface.BUIhandler.__init__`
|
|
|
|
:param app: Instance of the app we are running in
|
|
:type app: :class:`burpui.engines.server.BUIServer`
|
|
"""
|
|
self.app = app
|
|
self.users = {}
|
|
backends = []
|
|
self.errors = {}
|
|
if self.app.auth and "none" not in self.app.auth:
|
|
me, _ = os.path.splitext(os.path.basename(__file__))
|
|
back = self.app.auth
|
|
for au in back:
|
|
if au == me:
|
|
self.app.logger.critical("Recursive import not permitted!")
|
|
continue
|
|
try:
|
|
(modpath, _) = __name__.rsplit(".", 1)
|
|
mod = import_module("." + au, modpath)
|
|
obj = mod.UserHandler(self.app)
|
|
backends.append(obj)
|
|
except:
|
|
import traceback
|
|
|
|
self.errors[au] = traceback.format_exc()
|
|
for name, plugin in self.app.plugin_manager.get_plugins_by_type("auth").items():
|
|
try:
|
|
obj = plugin.UserHandler(self.app)
|
|
backends.append(obj)
|
|
except:
|
|
import traceback
|
|
|
|
self.errors[name] = traceback.format_exc()
|
|
backends.sort(key=lambda x: getattr(x, "priority", -1), reverse=True)
|
|
if not backends:
|
|
raise ImportError(
|
|
"No backend found for '{}':\n{}".format(self.app.auth, self.errors)
|
|
)
|
|
for name, err in self.errors.items():
|
|
self.app.logger.error(
|
|
"Unable to load module {}:\n{}".format(repr(name), err)
|
|
)
|
|
self.backends = OrderedDict()
|
|
for obj in backends:
|
|
self.backends[obj.name] = obj
|
|
|
|
def user(self, name=None, refresh=False):
|
|
"""See :func:`burpui.misc.auth.interface.BUIhandler.user`"""
|
|
key = session_manager.get_session_id() or name
|
|
if (
|
|
key != name
|
|
and is_uuid(name)
|
|
and name in self.users
|
|
and not session_manager.session_expired_by_id(name)
|
|
):
|
|
usr = self.users[name]
|
|
usr.id = key
|
|
self.users[key] = usr
|
|
del self.users[name]
|
|
session_manager.session_import_from(name)
|
|
session["authenticated"] = True
|
|
if not key:
|
|
return None
|
|
if refresh and key in self.users:
|
|
del self.users[key]
|
|
if session_manager.session_managed():
|
|
session_manager.session_expired()
|
|
if key not in self.users:
|
|
ret = UserHandler(self.app, self.backends, name, key, refresh)
|
|
if not ret.name or not ret.active:
|
|
return None
|
|
self.users[key] = ret
|
|
return ret
|
|
ret = self.users[key]
|
|
ret.refresh_session()
|
|
self.users[key] = ret
|
|
return ret
|
|
|
|
def remove(self, name):
|
|
"""See :func:`burpui.misc.auth.interface.BUIhandler.remove`"""
|
|
if name in self.users:
|
|
del self.users[name]
|
|
|
|
@property
|
|
def loader(self):
|
|
return None
|
|
|
|
|
|
class ProxyACLCall(object):
|
|
"""Class that actually calls the ACL method"""
|
|
|
|
def __init__(self, acl, username, method):
|
|
"""
|
|
:param acl: ACL to use
|
|
:type acl: :class:`burpui.misc.acl.interface.BUIacl`
|
|
|
|
:param username: username to check ACL for
|
|
:type username: str
|
|
|
|
:param method: Name of the method to proxify
|
|
:type method: str
|
|
"""
|
|
self.acl = acl
|
|
self.username = username
|
|
self.method = method
|
|
|
|
def __call__(self, *args, **kwargs):
|
|
"""This is where the proxy call (and the magic) occurs"""
|
|
# retrieve the original function prototype
|
|
proto = getattr(BUIacl, self.method)
|
|
args_name = list(proto.__code__.co_varnames)
|
|
# skip self
|
|
args_name.pop(0)
|
|
# skip username
|
|
args_name.pop(0)
|
|
# we transform unnamed arguments to named ones
|
|
# example:
|
|
# def my_function(toto, tata=None, titi=None):
|
|
#
|
|
# x = my_function('blah', titi='blih')
|
|
#
|
|
# => {'toto': 'blah', 'titi': 'blih'}
|
|
encoded_args = {"username": self.username}
|
|
for idx, opt in enumerate(args):
|
|
encoded_args[args_name[idx]] = opt
|
|
encoded_args.update(kwargs)
|
|
|
|
func = getattr(self.acl, self.method)
|
|
return func(**encoded_args)
|
|
|
|
|
|
class ACLproxy(BUIacl):
|
|
foreign = ACL_METHODS
|
|
BUIacl.__abstractmethods__ = frozenset()
|
|
|
|
def __init__(self, acl, username):
|
|
self.acl = acl
|
|
self.username = username
|
|
|
|
def __getattribute__(self, name):
|
|
# always return this value because we need it and if we don't do that
|
|
# we'll end up with an infinite loop
|
|
if name in ["foreign", "acl", "username"]:
|
|
return object.__getattribute__(self, name)
|
|
# now we can retrieve the 'foreign' list and know if the object called
|
|
# needs to be "proxyfied"
|
|
if name in self.foreign:
|
|
if self.acl:
|
|
return ProxyACLCall(self.acl, self.username, name)
|
|
# no ACL, assume true
|
|
return ProxyTrue()
|
|
return object.__getattribute__(self, name)
|
|
|
|
|
|
class ProxyTrue(object):
|
|
def __call__(self, *args, **kwargs):
|
|
return True
|
|
|
|
|
|
class ProxyFalse(object):
|
|
def __call__(self, *args, **kwargs):
|
|
return False
|
|
|
|
|
|
class ACLanon(BUIacl):
|
|
foreign = ACL_METHODS
|
|
BUIacl.__abstractmethods__ = frozenset()
|
|
|
|
def __getattribute__(self, name):
|
|
# always return this value because we need it and if we don't do that
|
|
# we'll end up with an infinite loop
|
|
if name == "foreign":
|
|
return object.__getattribute__(self, name)
|
|
# now we can retrieve the 'foreign' list and know if the object called
|
|
# needs to be "proxyfied"
|
|
if name in self.foreign:
|
|
return ProxyFalse()
|
|
return object.__getattribute__(self, name)
|
|
|
|
|
|
class BUIanon(AnonymousUserMixin):
|
|
_acl = ACLanon()
|
|
name = "Unknown"
|
|
|
|
def login(self, passwd=None):
|
|
return False
|
|
|
|
@property
|
|
def acl(self):
|
|
return self._acl
|
|
|
|
@property
|
|
def is_admin(self):
|
|
return False
|
|
|
|
@property
|
|
def is_moderator(self):
|
|
return False
|
|
|
|
|
|
class UserHandler(BUIuser):
|
|
"""See :class:`burpui.misc.auth.interface.BUIuser`"""
|
|
|
|
def __init__(self, app, backends=None, name=None, id=None, refresh=False):
|
|
"""
|
|
:param app: Application context
|
|
:type app: :class:`burpui.engines.server.BUIServer`
|
|
"""
|
|
self.id = id
|
|
self.app = app
|
|
self.active = False
|
|
last_backend = session.get("backend", None)
|
|
self.authenticated = session.get("authenticated", False)
|
|
self.language = session.get("language", None)
|
|
self.backends = backends
|
|
self.back = None
|
|
|
|
if refresh or not is_uuid(name):
|
|
username = name
|
|
else:
|
|
username = session_manager.get_session_username() or session.get("login")
|
|
self.real = None
|
|
|
|
self.name = username
|
|
if not self.name:
|
|
return
|
|
|
|
def _load_from_backend(backend, username):
|
|
if not backend:
|
|
return False
|
|
user = backend.user(username)
|
|
if not user:
|
|
return False
|
|
ext = user.get_id()
|
|
if ext:
|
|
self.real = user
|
|
self.active = True
|
|
self.name = ext
|
|
self.back = backend
|
|
return True
|
|
return False
|
|
|
|
if refresh:
|
|
for _, back in self.backends.items():
|
|
if _load_from_backend(back, self.name):
|
|
break
|
|
elif last_backend:
|
|
back = self.backends.get(last_backend)
|
|
_load_from_backend(back, self.name)
|
|
|
|
self._acl = ACLproxy(self.app.acl, self.name)
|
|
# now load the available prefs
|
|
self._load_prefs()
|
|
|
|
@property
|
|
def acl(self):
|
|
return self._acl
|
|
|
|
@property
|
|
def is_admin(self):
|
|
return self.acl.is_admin()
|
|
|
|
@property
|
|
def is_moderator(self):
|
|
return self.acl.is_moderator()
|
|
|
|
@property
|
|
def backend(self):
|
|
return getattr(self.real, "backend", None)
|
|
|
|
def _load_prefs(self):
|
|
session["login"] = self.name
|
|
if self.app.config["WITH_SQL"]:
|
|
from ...models import Pref
|
|
|
|
prefs = Pref.query.filter_by(user=self.name).all()
|
|
for pref in prefs:
|
|
if pref.key == "language":
|
|
continue
|
|
if hasattr(self, pref.key):
|
|
setattr(self, pref.key, pref.value)
|
|
session[pref.key] = pref.value
|
|
|
|
def refresh_session(self):
|
|
self.authenticated = session.get("authenticated", False)
|
|
self.language = session.get("language", None)
|
|
self._load_prefs()
|
|
|
|
def login(self, passwd=None):
|
|
"""See :func:`burpui.misc.auth.interface.BUIuser.login`"""
|
|
if not self.real:
|
|
self.authenticated = False
|
|
for name, back in self.backends.items():
|
|
user = back.user(self.name)
|
|
if not user:
|
|
continue
|
|
res = user.get_id()
|
|
if user.login(passwd):
|
|
self.authenticated = True
|
|
self.real = user
|
|
self.back = back
|
|
self.name = res
|
|
self._acl.username = res
|
|
break
|
|
elif self.real: # pragma: no cover
|
|
if self.back and getattr(self.back, "changed", False):
|
|
self.real = None
|
|
self.back = None
|
|
return self.login(passwd)
|
|
self.authenticated = self.real.login(passwd)
|
|
# allow to try another backend
|
|
if not self.authenticated:
|
|
self.real = None
|
|
self.back = None
|
|
return self.login(passwd)
|
|
session["authenticated"] = self.authenticated
|
|
session["language"] = self.language
|
|
session["login"] = self.name
|
|
session["backend"] = self.backend
|
|
return self.authenticated
|