single-shot auth using headers

This commit is contained in:
Devaev Maxim 2019-04-27 05:16:00 +03:00
parent 3476f52da9
commit 493d160a6e
7 changed files with 46 additions and 25 deletions

View File

@ -47,33 +47,40 @@ class AuthManager:
) -> None:
self.__internal_service = get_auth_service_class(internal_type)(**internal_kwargs)
get_logger().info("Using internal login service %r", self.__internal_service.PLUGIN_NAME)
get_logger().info("Using internal auth service %r", self.__internal_service.PLUGIN_NAME)
self.__external_service: Optional[BaseAuthService] = None
if external_type:
self.__external_service = get_auth_service_class(external_type)(**external_kwargs)
get_logger().info("Using external login service %r", self.__external_service.PLUGIN_NAME)
get_logger().info("Using external auth service %r", self.__external_service.PLUGIN_NAME)
self.__internal_users = internal_users
self.__tokens: Dict[str, str] = {} # {token: user}
async def login(self, user: str, passwd: str) -> Optional[str]:
async def authorize(self, user: str, passwd: str) -> bool:
if user not in self.__internal_users and self.__external_service:
service = self.__external_service
else:
service = self.__internal_service
if (await service.login(user, passwd)):
ok = (await service.authorize(user, passwd))
if ok:
get_logger().info("Authorized user %r via auth service %r", user, service.PLUGIN_NAME)
else:
get_logger().error("Got access denied for user %r from auth service %r", user, service.PLUGIN_NAME)
return ok
async def login(self, user: str, passwd: str) -> Optional[str]:
if (await self.authorize(user, passwd)):
for (token, token_user) in self.__tokens.items():
if user == token_user:
return token
token = secrets.token_hex(32)
self.__tokens[token] = user
get_logger().info("Logged in user %r via login service %r", user, service.PLUGIN_NAME)
get_logger().info("Logged in user %r", user)
return token
else:
get_logger().error("Access denied for user %r from login service %r", user, service.PLUGIN_NAME)
return None
def logout(self, token: str) -> None:

View File

@ -82,11 +82,11 @@ except ImportError:
from aiohttp.helpers import AccessLogger # type: ignore # pylint: disable=ungrouped-imports
_ATTR_KVMD_USER = "kvmd_user"
_ATTR_KVMD_AUTH_INFO = "kvmd_auth_info"
def _format_P(request: aiohttp.web.BaseRequest, *_, **__) -> str: # type: ignore # pylint: disable=invalid-name
return (getattr(request, _ATTR_KVMD_USER, None) or "-")
return (getattr(request, _ATTR_KVMD_AUTH_INFO, None) or "-")
AccessLogger._format_P = staticmethod(_format_P) # type: ignore # pylint: disable=protected-access
@ -148,6 +148,9 @@ _ATTR_EXPOSED_METHOD = "exposed_method"
_ATTR_EXPOSED_PATH = "exposed_path"
_ATTR_SYSTEM_TASK = "system_task"
_HEADER_AUTH_USER = "X-KVMD-User"
_HEADER_AUTH_PASSWD = "X-KVMD-Passwd"
_COOKIE_AUTH_TOKEN = "auth_token"
@ -156,12 +159,23 @@ def _exposed(http_method: str, path: str, auth_required: bool=True) -> Callable:
async def wrap(self: "Server", request: aiohttp.web.Request) -> aiohttp.web.Response:
try:
if auth_required:
user = request.headers.get(_HEADER_AUTH_USER, "")
passwd = request.headers.get(_HEADER_AUTH_PASSWD, "")
token = request.cookies.get(_COOKIE_AUTH_TOKEN, "")
if token:
if user:
user = valid_user(user)
setattr(request, _ATTR_KVMD_AUTH_INFO, "%s (xhdr)" % (user))
if not (await self._auth_manager.authorize(user, valid_passwd(passwd))):
raise ForbiddenError("Forbidden")
elif token:
user = self._auth_manager.check(valid_auth_token(token))
if not user:
setattr(request, _ATTR_KVMD_AUTH_INFO, "- (token)")
raise ForbiddenError("Forbidden")
setattr(request, _ATTR_KVMD_USER, user)
setattr(request, _ATTR_KVMD_AUTH_INFO, "%s (token)" % (user))
else:
raise UnauthorizedError("Unauthorized")

View File

@ -28,7 +28,7 @@ from .. import get_plugin_class
# =====
class BaseAuthService(BasePlugin):
async def login(self, user: str, passwd: str) -> bool:
async def authorize(self, user: str, passwd: str) -> bool:
raise NotImplementedError # pragma: nocover
async def cleanup(self) -> None:

View File

@ -44,6 +44,6 @@ class Plugin(BaseAuthService):
"file": Option("/etc/kvmd/htpasswd", type=valid_abs_path_exists, unpack_as="path"),
}
async def login(self, user: str, passwd: str) -> bool:
async def authorize(self, user: str, passwd: str) -> bool:
htpasswd = passlib.apache.HtpasswdFile(self.__path)
return htpasswd.check_password(user, passwd)

View File

@ -69,7 +69,7 @@ class Plugin(BaseAuthService):
"timeout": Option(5.0, type=valid_float_f01),
}
async def login(self, user: str, passwd: str) -> bool:
async def authorize(self, user: str, passwd: str) -> bool:
session = self.__ensure_session()
try:
async with session.request(

View File

@ -39,16 +39,16 @@ async def test_ok__htpasswd_service(tmpdir) -> None: # type: ignore
htpasswd.save()
async with get_configured_auth_service("htpasswd", file=path) as service:
assert not (await service.login("user", "foo"))
assert not (await service.login("admin", "foo"))
assert not (await service.login("user", "pass"))
assert (await service.login("admin", "pass"))
assert not (await service.authorize("user", "foo"))
assert not (await service.authorize("admin", "foo"))
assert not (await service.authorize("user", "pass"))
assert (await service.authorize("admin", "pass"))
htpasswd.set_password("admin", "bar")
htpasswd.set_password("user", "bar")
htpasswd.save()
assert (await service.login("admin", "bar"))
assert (await service.login("user", "bar"))
assert not (await service.login("admin", "foo"))
assert not (await service.login("user", "foo"))
assert (await service.authorize("admin", "bar"))
assert (await service.authorize("user", "bar"))
assert not (await service.authorize("admin", "foo"))
assert not (await service.authorize("user", "foo"))

View File

@ -73,7 +73,7 @@ async def test_ok(auth_server_port: int, kwargs: Dict) -> None:
("auth_plus_basic" if kwargs.get("user") else "auth"),
)
async with get_configured_auth_service("http", url=url, **kwargs) as service:
assert not (await service.login("user", "foobar"))
assert not (await service.login("admin", "foobar"))
assert not (await service.login("user", "pass"))
assert (await service.login("admin", "pass"))
assert not (await service.authorize("user", "foobar"))
assert not (await service.authorize("admin", "foobar"))
assert not (await service.authorize("user", "pass"))
assert (await service.authorize("admin", "pass"))