This repository has been archived on 2024-05-31. You can view files and clone it, but cannot push or open issues or pull requests.
authentik/passbook/policies/engine.py
Jens Langhammer 8dd05d5431 Squashed commit of the following:
commit 270739a45a
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Thu May 28 21:50:43 2020 +0200

    admin: fix policy testing form not showing the correct result

commit df8995deed
Author: Jens L <jens@beryju.org>
Date:   Thu May 28 21:45:54 2020 +0200

    policies/*: remove Policy.negate, order, timeout (#39)

    policies: rewrite engine to use PolicyBinding for order/negate/timeout
    policies: rewrite engine to use PolicyResult instead of tuple

commit fdfc6472d2
Author: Jens Langhammer <jens.langhammer@beryju.org>
Date:   Thu May 28 10:36:10 2020 +0200

    admin: fixup some urls

commit bc495828e7
Author: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date:   Thu May 28 09:39:28 2020 +0200

    build(deps): bump django-redis from 4.11.0 to 4.12.1 (#38)

    Bumps [django-redis](https://github.com/jazzband/django-redis) from 4.11.0 to 4.12.1.
    - [Release notes](https://github.com/jazzband/django-redis/releases)
    - [Changelog](https://github.com/jazzband/django-redis/blob/master/CHANGES.rst)
    - [Commits](https://github.com/jazzband/django-redis/compare/4.11.0...4.12.1)

    Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

    Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>

commit fa138a273f
Author: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
Date:   Thu May 28 08:59:19 2020 +0200

    build(deps): bump boto3 from 1.13.17 to 1.13.18 (#37)

    Bumps [boto3](https://github.com/boto/boto3) from 1.13.17 to 1.13.18.
    - [Release notes](https://github.com/boto/boto3/releases)
    - [Changelog](https://github.com/boto/boto3/blob/develop/CHANGELOG.rst)
    - [Commits](https://github.com/boto/boto3/compare/1.13.17...1.13.18)

    Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

    Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-05-28 21:56:18 +02:00

117 lines
4.3 KiB
Python

"""passbook policy engine"""
from multiprocessing import Pipe, set_start_method
from multiprocessing.connection import Connection
from typing import List, Optional
from django.core.cache import cache
from django.http import HttpRequest
from structlog import get_logger
from passbook.core.models import User
from passbook.policies.models import Policy, PolicyBinding, PolicyBindingModel
from passbook.policies.process import PolicyProcess, cache_key
from passbook.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
# This is only really needed for macOS, because Python 3.8 changed the default to spawn
# spawn causes issues with objects that aren't picklable, and also the django setup
set_start_method("fork")
class PolicyProcessInfo:
"""Dataclass to hold all information and communication channels to a process"""
process: PolicyProcess
connection: Connection
result: Optional[PolicyResult]
binding: PolicyBinding
def __init__(
self, process: PolicyProcess, connection: Connection, binding: PolicyBinding
):
self.process = process
self.connection = connection
self.binding = binding
self.result = None
class PolicyEngine:
"""Orchestrate policy checking, launch tasks and return result"""
use_cache: bool = True
request: PolicyRequest
__pbm: PolicyBindingModel
__cached_policies: List[PolicyResult]
__processes: List[PolicyProcessInfo]
def __init__(
self, pbm: PolicyBindingModel, user: User, request: HttpRequest = None
):
if not isinstance(pbm, PolicyBindingModel):
raise ValueError(f"{pbm} is not instance of PolicyBindingModel")
self.__pbm = pbm
self.request = PolicyRequest(user)
if request:
self.request.http_request = request
self.__cached_policies = []
self.__processes = []
def _iter_bindings(self) -> List[PolicyBinding]:
"""Make sure all Policies are their respective classes"""
return PolicyBinding.objects.filter(target=self.__pbm, enabled=True).order_by(
"order"
)
def _check_policy_type(self, policy: Policy):
"""Check policy type, make sure it's not the root class as that has no logic implemented"""
# policy_type = type(policy)
if policy.__class__ == Policy:
raise TypeError(f"Policy '{policy}' is root type")
def build(self) -> "PolicyEngine":
"""Build task group"""
for binding in self._iter_bindings():
self._check_policy_type(binding.policy)
policy = binding.policy
cached_policy = cache.get(cache_key(binding, self.request.user), None)
if cached_policy and self.use_cache:
LOGGER.debug("P_ENG: Taking result from cache", policy=policy)
self.__cached_policies.append(cached_policy)
continue
LOGGER.debug("P_ENG: Evaluating policy", policy=policy)
our_end, task_end = Pipe(False)
task = PolicyProcess(binding, self.request, task_end)
LOGGER.debug("P_ENG: Starting Process", policy=policy)
task.start()
self.__processes.append(
PolicyProcessInfo(process=task, connection=our_end, binding=binding)
)
# If all policies are cached, we have an empty list here.
for proc_info in self.__processes:
proc_info.process.join(proc_info.binding.timeout)
# Only call .recv() if no result is saved, otherwise we just deadlock here
if not proc_info.result:
proc_info.result = proc_info.connection.recv()
return self
@property
def result(self) -> PolicyResult:
"""Get policy-checking result"""
messages: List[str] = []
process_results: List[PolicyResult] = [
x.result for x in self.__processes if x.result
]
for result in process_results + self.__cached_policies:
LOGGER.debug("P_ENG: result", passing=result.passing)
if result.messages:
messages += result.messages
if not result.passing:
return PolicyResult(False, *messages)
return PolicyResult(True, *messages)
@property
def passing(self) -> bool:
"""Only get true/false if user passes"""
return self.result.passing