policies: rewrite cache_key to prevent wrong cache

# Conflicts:
#	passbook/core/signals.py
#	passbook/policies/engine.py
#	passbook/policies/process.py
This commit is contained in:
Jens Langhammer 2020-05-24 02:06:54 +02:00
parent f22c89c998
commit 55fc5a6068
7 changed files with 45 additions and 37 deletions

View file

@ -1,31 +1,7 @@
"""passbook core signals"""
from django.core.cache import cache
from django.core.signals import Signal
from django.db.models.signals import post_save
from django.dispatch import receiver
from structlog import get_logger
LOGGER = get_logger()
user_signed_up = Signal(providing_args=["request", "user"])
invitation_created = Signal(providing_args=["request", "invitation"])
invitation_used = Signal(providing_args=["request", "invitation", "user"])
password_changed = Signal(providing_args=["user", "password"])
@receiver(post_save)
# pylint: disable=unused-argument
def invalidate_policy_cache(sender, instance, **_):
"""Invalidate Policy cache when policy is updated"""
from passbook.policies.models import Policy, PolicyBinding
from passbook.policies.process import cache_key
if isinstance(instance, Policy):
LOGGER.debug("Invalidating policy cache", policy=instance)
total = 0
for binding in PolicyBinding.objects.filter(policy=instance):
prefix = cache_key(binding) + "*"
keys = cache.keys(prefix)
total += len(keys)
cache.delete_many(keys)
LOGGER.debug("Deleted keys", len=total)

View file

@ -20,9 +20,7 @@ def create_self_signed(apps, schema_editor):
class Migration(migrations.Migration):
dependencies = [
('passbook_crypto', '0001_initial'),
("passbook_crypto", "0001_initial"),
]
operations = [
migrations.RunPython(create_self_signed)
]
operations = [migrations.RunPython(create_self_signed)]

View file

@ -1,4 +1,6 @@
"""passbook policies app config"""
from importlib import import_module
from django.apps import AppConfig
@ -8,3 +10,7 @@ class PassbookPoliciesConfig(AppConfig):
name = "passbook.policies"
label = "passbook_policies"
verbose_name = "passbook Policies"
def ready(self):
"""Load source_types from config file"""
import_module("passbook.policies.signals")

View file

@ -73,17 +73,16 @@ class PolicyEngine:
"""Build task group"""
for binding in self._iter_bindings():
self._check_policy_type(binding.policy)
policy = binding.policy
key = cache_key(binding, self.request.user)
key = cache_key(binding, self.request)
cached_policy = cache.get(key, None)
if cached_policy and self.use_cache:
LOGGER.debug("P_ENG: Taking result from cache", policy=policy, cache_key=key)
LOGGER.debug("P_ENG: Taking result from cache", policy=binding.policy, cache_key=key)
self.__cached_policies.append(cached_policy)
continue
LOGGER.debug("P_ENG: Evaluating policy", policy=policy)
LOGGER.debug("P_ENG: Evaluating policy", policy=binding.policy)
our_end, task_end = Pipe(False)
task = PolicyProcess(binding, self.request, task_end)
LOGGER.debug("P_ENG: Starting Process", policy=policy)
LOGGER.debug("P_ENG: Starting Process", policy=binding.policy)
task.start()
self.__processes.append(
PolicyProcessInfo(process=task, connection=our_end, binding=binding)

View file

@ -14,11 +14,13 @@ from passbook.policies.types import PolicyRequest, PolicyResult
LOGGER = get_logger()
def cache_key(binding: PolicyBinding, user: Optional[User] = None) -> str:
def cache_key(binding: PolicyBinding, request: PolicyRequest) -> str:
"""Generate Cache key for policy"""
prefix = f"policy_{binding.policy_binding_uuid.hex}_{binding.policy.pk.hex}"
if user:
prefix += f"#{user.pk}"
if request.http_request:
prefix += f"_{request.http_request.session.session_key}"
if request.user:
prefix += f"#{request.user.pk}"
return prefix
@ -65,7 +67,7 @@ class PolicyProcess(Process):
passing=policy_result.passing,
user=self.request.user,
)
key = cache_key(self.binding, self.request.user)
key = cache_key(self.binding, self.request)
cache.set(key, policy_result)
LOGGER.debug("P_ENG(proc): Cached policy evaluation", key=key)
return policy_result

View file

@ -0,0 +1,25 @@
"""passbook policy signals"""
from django.core.cache import cache
from django.db.models.signals import post_save
from django.dispatch import receiver
from structlog import get_logger
LOGGER = get_logger()
@receiver(post_save)
# pylint: disable=unused-argument
def invalidate_policy_cache(sender, instance, **_):
"""Invalidate Policy cache when policy is updated"""
from passbook.policies.models import Policy, PolicyBinding
from passbook.policies.process import cache_key
if isinstance(instance, Policy):
LOGGER.debug("Invalidating policy cache", policy=instance)
total = 0
for binding in PolicyBinding.objects.filter(policy=instance):
prefix = f"policy_{binding.policy_binding_uuid.hex}_{binding.policy.pk.hex}" + "*"
keys = cache.keys(prefix)
total += len(keys)
cache.delete_many(keys)
LOGGER.debug("Deleted keys", len=total)

View file

@ -72,6 +72,7 @@ class TestUserWriteStage(TestCase):
plan.context[PLAN_CONTEXT_PROMPT] = {
"username": "test-user-new",
"password": new_password,
"some-custom-attribute": "test",
}
session = self.client.session
session[SESSION_KEY_PLAN] = plan
@ -88,6 +89,7 @@ class TestUserWriteStage(TestCase):
)
self.assertTrue(user_qs.exists())
self.assertTrue(user_qs.first().check_password(new_password))
self.assertEqual(user_qs.first().attributes["some-custom-attribute"], "test")
def test_without_data(self):
"""Test without data results in error"""