Browse Source

Cleanup misago.acl a little

rafalp 6 years ago
parent
commit
35bee7a9c0

+ 2 - 2
misago/acl/__init__.py

@@ -1,3 +1,3 @@
-from .api import get_user_acl, add_acl, serialize_acl
-
 default_app_config = 'misago.acl.apps.MisagoACLsConfig'
+
+ACL_CACHE = "acl"

+ 0 - 65
misago/acl/api.py

@@ -1,65 +0,0 @@
-"""
-Module functions for ACLs
-
-Workflow for ACLs in Misago is simple:
-
-First, you get user ACL. Its directory that you can introspect to find out user
-permissions, or if you have objects, you can use this acl to make those objects
-aware of their ACLs. This gives objects themselves special "acl" attribute with
-properties defined by ACL providers within their "add_acl_to_target"
-"""
-import copy
-
-from misago.core import threadstore
-from misago.core.cache import cache
-
-from . import version
-from .buildacl import build_acl
-from .providers import providers
-
-
-def get_user_acl(user):
-    """get ACL for User"""
-    acl_key = 'acl_%s' % user.acl_key
-
-    acl_cache = threadstore.get(acl_key)
-    if not acl_cache:
-        acl_cache = cache.get(acl_key)
-
-    if acl_cache and version.is_valid(acl_cache.get('_acl_version')):
-        return acl_cache
-    else:
-        new_acl = build_acl(user.get_roles())
-        new_acl['_acl_version'] = version.get_version()
-
-        threadstore.set(acl_key, new_acl)
-        cache.set(acl_key, new_acl)
-
-        return new_acl
-
-
-def add_acl(user_acl, target):
-    """add valid ACL to target (iterable of objects or single object)"""
-    if hasattr(target, '__iter__'):
-        for item in target:
-            _add_acl_to_target(user_acl, item)
-    else:
-        _add_acl_to_target(user_acl, target)
-
-
-def _add_acl_to_target(user_acl, target):
-    """add valid ACL to single target, helper for add_acl function"""
-    target.acl = {}
-
-    for annotator in providers.get_obj_type_annotators(target):
-        annotator(user_acl, target)
-
-
-def serialize_acl(target):
-    """serialize authenticated user's ACL"""
-    serialized_acl = copy.deepcopy(target.acl_cache)
-
-    for serializer in providers.get_obj_type_serializers(target):
-        serializer(serialized_acl)
-
-    return serialized_acl

+ 1 - 0
misago/acl/apps.py

@@ -1,6 +1,7 @@
 from django.apps import AppConfig
 from .providers import providers
 
+
 class MisagoACLsConfig(AppConfig):
     name = 'misago.acl'
     label = 'misago_acl'

+ 23 - 0
misago/acl/cache.py

@@ -0,0 +1,23 @@
+from django.core.cache import cache
+
+from misago.cache.versions import invalidate_cache
+
+from . import ACL_CACHE
+
+
+def get(user, cache_versions):
+    key = get_cache_key(user, cache_versions)
+    return cache.get(key)
+
+
+def set(user, cache_versions, user_acl):
+    key = get_cache_key(user, cache_versions)
+    cache.set(key, user_acl)
+
+
+def get_cache_key(user, cache_versions):
+    return 'acl_%s_%s' % (user.acl_key, cache_versions[ACL_CACHE])
+
+
+def clear():
+    invalidate_cache(ACL_CACHE)

+ 0 - 1
misago/acl/constants.py

@@ -1 +0,0 @@
-ACL_CACHEBUSTER = 'misago_acl'

+ 2 - 1
misago/acl/migrations/0004_cache_version.py

@@ -1,6 +1,7 @@
 # -*- coding: utf-8 -*-
 from django.db import migrations
 
+from misago.acl import ACL_CACHE
 from misago.cache.operations import StartCacheVersioning
 
 
@@ -12,5 +13,5 @@ class Migration(migrations.Migration):
     ]
 
     operations = [
-        StartCacheVersioning("acl")
+        StartCacheVersioning(ACL_CACHE)
     ]

+ 3 - 3
misago/acl/models.py

@@ -2,7 +2,7 @@ from django.contrib.postgres.fields import JSONField
 from django.db import models
 from django.utils.translation import gettext as _
 
-from . import version as acl_version
+from . import cache
 
 
 def permissions_default():
@@ -22,11 +22,11 @@ class BaseRole(models.Model):
 
     def save(self, *args, **kwargs):
         if self.pk:
-            acl_version.invalidate()
+            cache.clear()
         return super().save(*args, **kwargs)
 
     def delete(self, *args, **kwargs):
-        acl_version.invalidate()
+        cache.clear()
         return super().delete(*args, **kwargs)
 
 

+ 18 - 0
misago/acl/objectacl.py

@@ -0,0 +1,18 @@
+from .providers import providers
+
+
+def add_acl_to_obj(user_acl, obj):
+    """add valid ACL to obj (iterable of objects or single object)"""
+    if hasattr(obj, '__iter__'):
+        for item in obj:
+            _add_acl_to_obj(user_acl, item)
+    else:
+        _add_acl_to_obj(user_acl, obj)
+
+
+def _add_acl_to_obj(user_acl, obj):
+    """add valid ACL to single obj, helper for add_acl function"""
+    obj.acl = {}
+
+    for annotator in providers.get_obj_type_annotators(obj):
+        annotator(user_acl, obj)

+ 14 - 12
misago/acl/providers.py

@@ -5,7 +5,7 @@ from misago.conf import settings
 
 _NOT_INITIALIZED_ERROR = (
     "PermissionProviders instance has to load providers with load() "
-    "before get_obj_type_annotators(), get_obj_type_serializers(), "
+    "before get_obj_type_annotators(), get_obj_type_user_acl_serializers(), "
     "list() or dict() methods will be available."
 )
 
@@ -24,14 +24,16 @@ class PermissionProviders(object):
         self._providers_dict = {}
 
         self._annotators = {}
-        self._serializers = {}
+        self._user_acl_serializers = []
 
     def load(self):
-        if not self._initialized:
-            self._register_providers()
-            self._change_lists_to_tupes(self._annotators)
-            self._change_lists_to_tupes(self._serializers)
-            self._initialized = True
+        if self._initialized:
+            raise RuntimeError("providers are already loaded")
+
+        self._register_providers()
+        self._cast_dict_values_to_tuples(self._annotators)
+        self._user_acl_serializers = tuple(self._user_acl_serializers)
+        self._initialized = True
 
     def _register_providers(self):
         for namespace in settings.MISAGO_ACL_EXTENSIONS:
@@ -41,7 +43,7 @@ class PermissionProviders(object):
             if hasattr(self._providers_dict[namespace], 'register_with'):
                 self._providers_dict[namespace].register_with(self)
 
-    def _change_lists_to_tupes(self, types_dict):
+    def _cast_dict_values_to_tuples(self, types_dict):
         for hashType in types_dict.keys():
             types_dict[hashType] = tuple(types_dict[hashType])
 
@@ -50,18 +52,18 @@ class PermissionProviders(object):
         assert not self._initialized, _ALREADY_INITIALIZED_ERROR
         self._annotators.setdefault(hashable_type, []).append(func)
 
-    def acl_serializer(self, hashable_type, func):
+    def user_acl_serializer(self, func):
         """registers ACL serializer for specified types"""
         assert not self._initialized, _ALREADY_INITIALIZED_ERROR
-        self._serializers.setdefault(hashable_type, []).append(func)
+        self._user_acl_serializers.append(func)
 
     def get_obj_type_annotators(self, obj):
         assert self._initialized, _NOT_INITIALIZED_ERROR
         return self._annotators.get(obj.__class__, [])
 
-    def get_obj_type_serializers(self, obj):
+    def get_user_acl_serializers(self):
         assert self._initialized, _NOT_INITIALIZED_ERROR
-        return self._serializers.get(obj.__class__, [])
+        return self._user_acl_serializers
 
     def list(self):
         assert self._initialized, _NOT_INITIALIZED_ERROR

+ 0 - 26
misago/acl/tests/test_api.py

@@ -1,26 +0,0 @@
-from django.contrib.auth import get_user_model
-from django.test import TestCase
-
-from misago.acl.api import get_user_acl
-from misago.users.models import AnonymousUser
-
-
-UserModel = get_user_model()
-
-
-class GetUserACLTests(TestCase):
-    def test_get_authenticated_acl(self):
-        """get ACL for authenticated user"""
-        test_user = UserModel.objects.create_user('Bob', 'bob@bob.com', 'pass123')
-
-        acl = get_user_acl(test_user)
-
-        self.assertTrue(acl)
-        self.assertEqual(acl, test_user.acl_cache)
-
-    def test_get_anonymous_acl(self):
-        """get ACL for unauthenticated user"""
-        acl = get_user_acl(AnonymousUser())
-
-        self.assertTrue(acl)
-        self.assertEqual(acl, AnonymousUser().acl_cache)

+ 7 - 0
misago/acl/tests/test_getting_user_acl.py

@@ -29,6 +29,13 @@ class GettingUserACLTests(TestCase):
         assert acl["is_staff"] is False
         assert acl["is_superuser"] is False
 
+    def test_user_acl_includes_cache_versions(self):
+        user = User.objects.create_user('Bob', 'bob@bob.com')
+        acl = get_user_acl(user, cache_versions)
+
+        assert acl
+        assert acl["cache_versions"] == cache_versions
+
     def test_getter_returns_anonymous_user_acl(self):
         user = AnonymousUser()
         acl = get_user_acl(user, cache_versions)

+ 46 - 73
misago/acl/tests/test_providers.py

@@ -1,112 +1,85 @@
-from types import ModuleType
-
 from django.test import TestCase
 
 from misago.acl.providers import PermissionProviders
 from misago.conf import settings
 
 
-class TestType(object):
-    pass
-
-
 class PermissionProvidersTests(TestCase):
-    def test_initialization(self):
-        """providers manager is lazily initialized"""
+    def test_providers_are_not_loaded_on_container_init(self):
         providers = PermissionProviders()
 
-        self.assertTrue(providers._initialized is False)
-        self.assertTrue(not providers._providers)
-        self.assertTrue(not providers._providers_dict)
-
-        # public api errors on non-loaded object
-        with self.assertRaises(AssertionError):
-            providers.get_obj_type_annotators(TestType())
-
-        with self.assertRaises(AssertionError):
-            providers.get_obj_type_serializers(TestType())
-
-        with self.assertRaises(AssertionError):
-            providers.list()
-
-        self.assertTrue(providers._initialized is False)
-        self.assertTrue(not providers._providers)
-        self.assertTrue(not providers._providers_dict)
+        assert not providers._initialized
+        assert not providers._providers
+        assert not providers._annotators
+        assert not providers._user_acl_serializers
 
-        # load initializes providers
+    def test_container_loads_providers(self):
         providers = PermissionProviders()
         providers.load()
 
-        self.assertTrue(providers._initialized)
-        self.assertTrue(providers._providers)
-        self.assertTrue(providers._providers_dict)
+        assert providers._providers
+        assert providers._annotators
+        assert providers._user_acl_serializers
 
-    def test_list(self):
-        """providers manager list() returns iterable of tuples"""
+    def test_loading_providers_second_time_raises_runtime_error(self):
         providers = PermissionProviders()
-
-        # providers.list() throws before loading providers
-        with self.assertRaises(AssertionError):
-            providers.list()
-
         providers.load()
 
-        providers_list = providers.list()
+        with self.assertRaises(RuntimeError):
+            providers.load()
 
+    def test_container_returns_list_of_providers(self):
+        providers = PermissionProviders()
+        providers.load()
+        
         providers_setting = settings.MISAGO_ACL_EXTENSIONS
-        self.assertEqual(len(providers_list), len(providers_setting))
-
-        for extension, module in providers_list:
-            self.assertTrue(isinstance(extension, str))
-            self.assertEqual(type(module), ModuleType)
+        self.assertEqual(len(providers.list()), len(providers_setting))
 
-    def test_dict(self):
-        """providers manager dict() returns dict"""
+    def test_container_returns_dict_of_providers(self):
         providers = PermissionProviders()
+        providers.load()
+        
+        providers_setting = settings.MISAGO_ACL_EXTENSIONS
+        self.assertEqual(len(providers.dict()), len(providers_setting))
 
-        # providers.dict() throws before loading providers
+    def test_accessing_providers_list_before_load_raises_assertion_error(self):
+        providers = PermissionProviders()
+        with self.assertRaises(AssertionError):
+            providers.list()
+    
+    def test_accessing_providers_dict_before_load_raises_assertion_error(self):
+        providers = PermissionProviders()
         with self.assertRaises(AssertionError):
             providers.dict()
 
-        providers.load()
-
-        providers_dict = providers.dict()
-
-        providers_setting = settings.MISAGO_ACL_EXTENSIONS
-        self.assertEqual(len(providers_dict), len(providers_setting))
+    def test_getter_returns_registered_type_annotator(self):
+        class TestType(object):
+            pass
 
-        for extension, module in providers_dict.items():
-            self.assertTrue(isinstance(extension, str))
-            self.assertEqual(type(module), ModuleType)
 
-    def test_annotators(self):
-        """its possible to register and get annotators"""
-        def mock_annotator(*args):
+        def test_annotator():
             pass
+        
 
         providers = PermissionProviders()
-        providers.acl_annotator(TestType, mock_annotator)
+        providers.acl_annotator(TestType, test_annotator)
         providers.load()
 
-        # providers.acl_annotator() throws after loading providers
-        with self.assertRaises(AssertionError):
-            providers.acl_annotator(TestType, mock_annotator)
+        assert test_annotator in providers.get_obj_type_annotators(TestType())
 
-        annotators_list = providers.get_obj_type_annotators(TestType())
-        self.assertEqual(annotators_list[0], mock_annotator)
+    def test_container_returns_list_of_user_acl_serializers(self):
+        providers = PermissionProviders()
+        providers.load()
 
-    def test_serializers(self):
-        """its possible to register and get annotators"""
-        def mock_serializer(*args):
+        assert providers.get_user_acl_serializers()
+
+    def test_getter_returns_registered_user_acl_serializer(self):
+        def test_user_acl_serializer():
             pass
 
+
         providers = PermissionProviders()
-        providers.acl_serializer(TestType, mock_serializer)
+        providers.user_acl_serializer(test_user_acl_serializer)
         providers.load()
 
-        # providers.acl_serializer() throws after loading providers
-        with self.assertRaises(AssertionError):
-            providers.acl_serializer(TestType, mock_serializer)
-
-        serializers_list = providers.get_obj_type_serializers(TestType())
-        self.assertEqual(serializers_list[0], mock_serializer)
+        assert test_user_acl_serializer in providers.get_user_acl_serializers()

+ 23 - 0
misago/acl/tests/test_serializing_user_acl.py

@@ -0,0 +1,23 @@
+import json
+
+from django.contrib.auth import get_user_model
+from django.test import TestCase
+
+from misago.acl.useracl import get_user_acl, serialize_user_acl
+
+User = get_user_model()
+
+cache_versions = {"acl": "abcdefgh"}
+
+
+class SerializingUserACLTests(TestCase):
+    def test_user_acl_is_serializeable(self):
+        user = User.objects.create_user('Bob', 'bob@bob.com')
+        acl = get_user_acl(user, cache_versions)
+        assert serialize_user_acl(acl)
+
+    def test_user_acl_is_json_serializeable(self):
+        user = User.objects.create_user('Bob', 'bob@bob.com')
+        acl = get_user_acl(user, cache_versions)
+        serialized_acl = serialize_user_acl(acl)
+        assert json.dumps(serialized_acl)

+ 17 - 5
misago/acl/useracl.py

@@ -1,17 +1,29 @@
-from django.core.cache import cache
+import copy
 
-from . import buildacl
+from . import buildacl, cache
+from .providers import providers
 
 
 def get_user_acl(user, cache_versions):
-    cache_name = 'acl_%s_%s' % (user.acl_key, cache_versions["acl"])
-    user_acl = cache.get(cache_name)
+    user_acl = cache.get(user, cache_versions)
     if user_acl is None:
         user_acl = buildacl.build_acl(user.get_roles())
-        cache.set(cache_name, user_acl)
+        cache.set(user, cache_versions, user_acl)
     user_acl["user_id"] = user.id
     user_acl["is_authenticated"] = bool(user.is_authenticated)
     user_acl["is_anonymous"] = bool(user.is_anonymous)
     user_acl["is_staff"] = user.is_staff
     user_acl["is_superuser"] = user.is_superuser
+    user_acl["cache_versions"] = cache_versions.copy()
     return user_acl
+
+
+def serialize_user_acl(user_acl):
+    """serialize authenticated user's ACL"""
+    serialized_acl = copy.deepcopy(user_acl)
+    serialized_acl.pop("cache_versions")
+
+    for serializer in providers.get_user_acl_serializers():
+        serializer(serialized_acl)
+
+    return serialized_acl

+ 0 - 15
misago/acl/version.py

@@ -1,15 +0,0 @@
-from misago.core import cachebuster
-
-from .constants import ACL_CACHEBUSTER
-
-
-def get_version():
-    return cachebuster.get_version(ACL_CACHEBUSTER)
-
-
-def is_valid(version):
-    return cachebuster.is_valid(ACL_CACHEBUSTER, version)
-
-
-def invalidate():
-    cachebuster.invalidate(ACL_CACHEBUSTER)