Просмотр исходного кода

Refactored ACL's and added tests harness

Rafał Pitoń 11 лет назад
Родитель
Сommit
de8f9766ec

+ 1 - 1
misago/acl/__init__.py

@@ -1 +1 @@
-from misago.acl.providers import *  # noqa
+from misago.acl.api import *  # noqa

+ 64 - 0
misago/acl/api.py

@@ -0,0 +1,64 @@
+from misago.core import threadstore
+from misago.core.cache import cache
+from misago.acl import cachebuster
+from misago.acl.builder import build_acl
+from misago.acl.providers import providers
+
+
+__ALL__ = ['get_user_acl', 'add_acl']
+
+
+"""
+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"
+"""
+
+
+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 cachebuster.is_valid(acl_cache.get('_acl_version')):
+        return acl_cache
+    else:
+        new_acl = build_acl(user.get_roles())
+        new_acl['_acl_version'] = cachebuster.get_version()
+
+        threadstore.set(acl_key, new_acl)
+        cache.set(acl_key, new_acl)
+
+        return new_acl
+
+
+def add_acl(acl, target):
+    """
+    Add valid ACL to target (iterable of objects or single object)
+    """
+    try:
+        for item in target:
+            _add_acl_to_target(acl, target)
+    except TypeError:
+        _add_acl_to_target(acl, target)
+
+
+def _add_acl_to_target(acl, target):
+    """
+    Add valid ACL to single target, helper for add_acl function
+    """
+    target.acl = {}
+
+    for provider, module in providers.list():
+        if hasattr(module, 'add_acl_to_target'):
+            module.add_acl_to_target(acl, target)

+ 13 - 0
misago/acl/builder.py

@@ -0,0 +1,13 @@
+from misago.acl.providers import providers
+
+
+def build_acl(roles):
+    """
+    Build ACL for given roles
+    """
+    acl = {}
+
+    for provider, module in providers.list():
+        module.build_acl(acl, roles)
+
+    return acl

+ 34 - 0
misago/acl/forms.py

@@ -1,6 +1,7 @@
 from django.utils.translation import ugettext_lazy as _
 from misago.core import forms
 from misago.acl.models import Role
+from misago.acl.providers import providers
 
 
 class RoleForm(forms.ModelForm):
@@ -10,3 +11,36 @@ class RoleForm(forms.ModelForm):
         model = Role
         fields = ['name']
 
+
+def get_permissions_forms(role, data=None):
+    """
+    Utility function for building forms in admin
+    """
+    role_permissions = role.permissions
+
+    forms = []
+    for provider, module in providers.list():
+        try:
+            default_data = module.DEFAULT_PERMISSIONS
+        except AttributeError:
+            message = "'%s' object has no attribute '%s'"
+            raise AttributeError(
+                message % (provider, 'DEFAULT_PERMISSIONS'))
+        try:
+            module.change_permissions_form
+        except AttributeError:
+            message = "'%s' object has no attribute '%s'"
+            raise AttributeError(
+                message % (provider, 'change_permissions_form'))
+
+        FormType = module.change_permissions_form(role)
+
+        if FormType:
+            if data:
+                forms.append(FormType(data, prefix=provider))
+            else:
+                initial_data = role_permissions.get(provider, default_data)
+                forms.append(FormType(initial=initial_data,
+                                      prefix=provider))
+
+    return forms

+ 1 - 80
misago/acl/providers.py

@@ -2,8 +2,7 @@ from importlib import import_module
 from django.conf import settings
 
 
-__ALL__ = ['providers', 'get_default_permissions',
-           'get_change_permissions_forms']
+__ALL__ = ['providers']
 
 
 # Manager for permission providers
@@ -33,81 +32,3 @@ class PermissionProviders(object):
 
 
 providers = PermissionProviders()
-
-
-"""
-Module functions for ACLS
-
-Workflow for ACLs in Misago is simple:
-
-First, you get user ACL. You can introspect it directory 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"
-"""
-def get_user_acl(user):
-    """
-    Get hydrated ACL for User
-    """
-    pass
-
-
-def _add_acl_to_target(acl, target):
-    """
-    Add valid ACL to single target
-    """
-    for provider, module in providers.list():
-        module.add_acl_to_target(acl, target)
-
-
-def add_acl(acl, target):
-    """
-    Add valid ACL to target (iterable of objects or single object)
-    """
-    targets = []
-
-    try:
-        for item in target:
-            targets.append(item)
-    except TypeError:
-        targets.append(target)
-
-    for target in targets:
-        _add_acl_to_target(acl, target)
-
-
-"""
-Admin utils
-"""
-def get_change_permissions_forms(role, data=None):
-    """
-    Utility function for building forms in admin
-    """
-    role_permissions = role.permissions
-
-    forms = []
-    for provider, module in providers.list():
-        try:
-            default_data = module.DEFAULT_PERMISSIONS
-        except AttributeError:
-            message = "'%s' object has no attribute '%s'"
-            raise AttributeError(
-                message % (provider, 'DEFAULT_PERMISSIONS'))
-        try:
-            module.change_permissions_form
-        except AttributeError:
-            message = "'%s' object has no attribute '%s'"
-            raise AttributeError(
-                message % (provider, 'change_permissions_form'))
-
-        FormType = module.change_permissions_form(role)
-
-        if FormType:
-            if data:
-                forms.append(FormType(data, prefix=provider))
-            else:
-                initial_data = role_permissions.get(provider, default_data)
-                forms.append(FormType(initial=initial_data,
-                                      prefix=provider))
-
-    return forms

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

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

+ 55 - 0
misago/acl/tests/test_providers.py

@@ -0,0 +1,55 @@
+from types import ModuleType
+from django.conf import settings
+from django.test import TestCase
+from misago.acl.providers import PermissionProviders
+
+
+class PermissionProvidersTests(TestCase):
+    def test_initialization(self):
+        """providers manager is lazily initialized"""
+        providers = PermissionProviders()
+
+        self.assertTrue(providers._initialized is False)
+        self.assertTrue(not providers._providers)
+        self.assertTrue(not providers._providers_dict)
+
+        # list call initializes providers
+        providers_list = providers.list()
+
+        self.assertTrue(providers_list)
+        self.assertTrue(providers._initialized)
+        self.assertTrue(providers._providers)
+        self.assertTrue(providers._providers_dict)
+
+        # dict call initializes providers
+        providers = PermissionProviders()
+        providers_dict = providers.dict()
+
+        self.assertTrue(providers_dict)
+        self.assertTrue(providers._initialized)
+        self.assertTrue(providers._providers)
+        self.assertTrue(providers._providers_dict)
+
+    def test_list(self):
+        """providers manager list() returns iterable of tuples"""
+        providers = PermissionProviders()
+        providers_list = providers.list()
+
+        providers_setting = settings.MISAGO_PERMISSION_PROVIDERS
+        self.assertEqual(len(providers_list), len(providers_setting))
+
+        for provider, module in providers_list:
+            self.assertTrue(isinstance(provider, basestring))
+            self.assertEqual(type(module), ModuleType)
+
+    def test_dict(self):
+        """providers manager dict() returns dict"""
+        providers = PermissionProviders()
+        providers_dict = providers.dict()
+
+        providers_setting = settings.MISAGO_PERMISSION_PROVIDERS
+        self.assertEqual(len(providers_dict), len(providers_setting))
+
+        for provider, module in providers_dict.items():
+            self.assertTrue(isinstance(provider, basestring))
+            self.assertEqual(type(module), ModuleType)

+ 2 - 2
misago/acl/testutils.py

@@ -1,4 +1,4 @@
-from misago.acl import get_change_permissions_forms
+from misago.acl.forms import get_permissions_forms
 
 
 def fake_post_data(target, data_dict):
@@ -7,7 +7,7 @@ def fake_post_data(target, data_dict):
     to receive values. This function populates data dict with default values
     for permissions, making form validation pass
     """
-    for form in get_change_permissions_forms(target):
+    for form in get_permissions_forms(target):
         for field in form:
             if field.value() == True:
                 data_dict[field.html_name] = 1

+ 3 - 6
misago/acl/views.py

@@ -2,8 +2,7 @@ from django.contrib import messages
 from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy as _
 from misago.admin.views import generic
-from misago.acl import get_change_permissions_forms
-from misago.acl.forms import RoleForm
+from misago.acl.forms import RoleForm, get_permissions_forms
 from misago.acl.models import Role
 
 
@@ -23,10 +22,10 @@ class RoleFormMixin(object):
         role_permissions = target.permissions
         form = RoleForm(instance=target)
 
-        perms_forms = get_change_permissions_forms(target)
+        perms_forms = get_permissions_forms(target)
 
         if request.method == 'POST':
-            perms_forms = get_change_permissions_forms(target, request.POST)
+            perms_forms = get_permissions_forms(target, request.POST)
             valid_forms = 0
             for permissions_form in perms_forms:
                 if permissions_form.is_valid():
@@ -48,8 +47,6 @@ class RoleFormMixin(object):
                     return redirect(request.path)
                 else:
                     return redirect(self.root_link)
-        else:
-            perms_forms = get_change_permissions_forms(target)
 
         return self.render(
             request,

+ 11 - 1
misago/forums/permissions.py

@@ -9,6 +9,9 @@ DEFAULT_PERMISSIONS = {
 }
 
 
+"""
+Admin Permissions Form
+"""
 class PermissionsForm(forms.Form):
     legend = _("Forum access")
     can_see = forms.YesNoSwitch(label=_("Can see forum"))
@@ -16,7 +19,14 @@ class PermissionsForm(forms.Form):
 
 
 def change_permissions_form(role):
-    if role.__class__ == ForumRole:
+    if isinstance(role, ForumRole):
         return PermissionsForm
     else:
         return None
+
+
+"""
+ACL Builder
+"""
+def build_acl(acl, roles):
+    pass

+ 0 - 1
misago/forums/tests/test_permissions_admin_views.py

@@ -1,5 +1,4 @@
 from django.core.urlresolvers import reverse
-from misago.acl import get_change_permissions_forms
 from misago.acl.models import Role
 from misago.acl.testutils import fake_post_data
 from misago.admin.testutils import AdminTestCase

+ 4 - 5
misago/forums/views/permsadmin.py

@@ -2,7 +2,8 @@ from django.contrib import messages
 from django.shortcuts import redirect
 from django.utils.translation import ugettext_lazy as _
 from misago.admin.views import generic
-from misago.acl import cachebuster, get_change_permissions_forms
+from misago.acl import cachebuster
+from misago.acl.forms import get_permissions_forms
 from misago.acl.views import RoleAdmin, RolesList
 from misago.forums.forms import ForumRoleForm, RoleForumACLFormFactory
 from misago.forums.models import Forum, ForumRole, RoleForumACL
@@ -24,10 +25,10 @@ class RoleFormMixin(object):
         role_permissions = target.permissions
         form = ForumRoleForm(instance=target)
 
-        perms_forms = get_change_permissions_forms(target)
+        perms_forms = get_permissions_forms(target)
 
         if request.method == 'POST':
-            perms_forms = get_change_permissions_forms(target, request.POST)
+            perms_forms = get_permissions_forms(target, request.POST)
             valid_forms = 0
             for permissions_form in perms_forms:
                 if permissions_form.is_valid():
@@ -49,8 +50,6 @@ class RoleFormMixin(object):
                     return redirect(request.path)
                 else:
                     return redirect(self.root_link)
-        else:
-            perms_forms = get_change_permissions_forms(target)
 
         return self.render(
             request,

+ 14 - 3
misago/users/models/usermodel.py

@@ -104,6 +104,10 @@ class User(AbstractBaseUser, PermissionsMixin):
             self._acl_cache = get_user_acl(self)
             return self._acl_cache
 
+    @acl.setter
+    def acl(self, value):
+        raise TypeError('Cannot make User instances ACL aware')
+
     @property
     def staff_level(self):
         if self.is_superuser:
@@ -149,19 +153,19 @@ class User(AbstractBaseUser, PermissionsMixin):
         roles_pks = []
         roles_dict = {}
 
-        for role in self.role_set.all():
+        for role in self.roles.all():
             roles_pks.append(role.pk)
             roles_dict[role.pk] = role
 
         if self.rank:
-            for role in self.rank.role_set.all():
+            for role in self.rank.roles.all():
                 if role.pk not in roles_pks:
                     roles_pks.append(role.pk)
                     roles_dict[role.pk] = role
 
         return [roles_dict[r] for r in roles_pks]
 
-    def update_acl_token(self):
+    def update_acl_key(self):
         pass
 
 
@@ -176,12 +180,19 @@ class AnonymousUser(DjangoAnonymousUser):
             self._acl_cache = get_user_acl(self)
             return self._acl_cache
 
+    @acl.setter
+    def acl(self, value):
+        raise TypeError('Cannot make AnonymousUser instances ACL aware')
+
     def get_roles(self):
         try:
             return [Role.objects.get(special_role="anonymous")]
         except Role.DoesNotExist:
             raise RuntimeError("Anonymous user role not found.")
 
+    def update_acl_key(self):
+        raise TypeError("Can't update ACL key on anonymous users")
+
 
 """register model in misago admin"""
 site.add_node(

+ 11 - 1
misago/users/permissions/account.py

@@ -12,6 +12,9 @@ DEFAULT_PERMISSIONS = {
 }
 
 
+"""
+Admin Permissions Form
+"""
 class PermissionsForm(forms.Form):
     legend = _("Account settings")
     name_changes_allowed = forms.IntegerField(
@@ -34,7 +37,14 @@ class PermissionsForm(forms.Form):
 
 
 def change_permissions_form(role):
-    if role.__class__ == Role:
+    if isinstance(role, Role):
         return PermissionsForm
     else:
         return None
+
+
+"""
+ACL Builder
+"""
+def build_acl(acl, roles):
+    pass

+ 11 - 1
misago/users/permissions/destroying.py

@@ -9,6 +9,9 @@ DEFAULT_PERMISSIONS = {
 }
 
 
+"""
+Admin Permissions Form
+"""
 class PermissionsForm(forms.Form):
     legend = _("Destroying user accounts")
     can_destroy_user_newer_than = forms.IntegerField(
@@ -22,7 +25,14 @@ class PermissionsForm(forms.Form):
 
 
 def change_permissions_form(role):
-    if role.__class__ == Role and role.special_role != 'anonymous':
+    if isinstance(role, Role) and role.special_role != 'anonymous':
         return PermissionsForm
     else:
         return None
+
+
+"""
+ACL Builder
+"""
+def build_acl(acl, roles):
+    pass

+ 11 - 1
misago/users/permissions/profiles.py

@@ -11,6 +11,9 @@ DEFAULT_PERMISSIONS = {
 }
 
 
+"""
+Admin Permissions Form
+"""
 class PermissionsForm(forms.Form):
     legend = _("User profiles")
     can_search_users = forms.YesNoSwitch(
@@ -24,7 +27,14 @@ class PermissionsForm(forms.Form):
 
 
 def change_permissions_form(role):
-    if role.__class__ == Role:
+    if isinstance(role, Role):
         return PermissionsForm
     else:
         return None
+
+
+"""
+ACL Builder
+"""
+def build_acl(acl, roles):
+    pass