Browse Source

Refactored ACL's and added tests harness

Rafał Pitoń 11 years ago
parent
commit
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 django.utils.translation import ugettext_lazy as _
 from misago.core import forms
 from misago.core import forms
 from misago.acl.models import Role
 from misago.acl.models import Role
+from misago.acl.providers import providers
 
 
 
 
 class RoleForm(forms.ModelForm):
 class RoleForm(forms.ModelForm):
@@ -10,3 +11,36 @@ class RoleForm(forms.ModelForm):
         model = Role
         model = Role
         fields = ['name']
         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
 from django.conf import settings
 
 
 
 
-__ALL__ = ['providers', 'get_default_permissions',
-           'get_change_permissions_forms']
+__ALL__ = ['providers']
 
 
 
 
 # Manager for permission providers
 # Manager for permission providers
@@ -33,81 +32,3 @@ class PermissionProviders(object):
 
 
 
 
 providers = PermissionProviders()
 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):
 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
     to receive values. This function populates data dict with default values
     for permissions, making form validation pass
     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:
         for field in form:
             if field.value() == True:
             if field.value() == True:
                 data_dict[field.html_name] = 1
                 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.shortcuts import redirect
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from misago.admin.views import generic
 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
 from misago.acl.models import Role
 
 
 
 
@@ -23,10 +22,10 @@ class RoleFormMixin(object):
         role_permissions = target.permissions
         role_permissions = target.permissions
         form = RoleForm(instance=target)
         form = RoleForm(instance=target)
 
 
-        perms_forms = get_change_permissions_forms(target)
+        perms_forms = get_permissions_forms(target)
 
 
         if request.method == 'POST':
         if request.method == 'POST':
-            perms_forms = get_change_permissions_forms(target, request.POST)
+            perms_forms = get_permissions_forms(target, request.POST)
             valid_forms = 0
             valid_forms = 0
             for permissions_form in perms_forms:
             for permissions_form in perms_forms:
                 if permissions_form.is_valid():
                 if permissions_form.is_valid():
@@ -48,8 +47,6 @@ class RoleFormMixin(object):
                     return redirect(request.path)
                     return redirect(request.path)
                 else:
                 else:
                     return redirect(self.root_link)
                     return redirect(self.root_link)
-        else:
-            perms_forms = get_change_permissions_forms(target)
 
 
         return self.render(
         return self.render(
             request,
             request,

+ 11 - 1
misago/forums/permissions.py

@@ -9,6 +9,9 @@ DEFAULT_PERMISSIONS = {
 }
 }
 
 
 
 
+"""
+Admin Permissions Form
+"""
 class PermissionsForm(forms.Form):
 class PermissionsForm(forms.Form):
     legend = _("Forum access")
     legend = _("Forum access")
     can_see = forms.YesNoSwitch(label=_("Can see forum"))
     can_see = forms.YesNoSwitch(label=_("Can see forum"))
@@ -16,7 +19,14 @@ class PermissionsForm(forms.Form):
 
 
 
 
 def change_permissions_form(role):
 def change_permissions_form(role):
-    if role.__class__ == ForumRole:
+    if isinstance(role, ForumRole):
         return PermissionsForm
         return PermissionsForm
     else:
     else:
         return None
         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 django.core.urlresolvers import reverse
-from misago.acl import get_change_permissions_forms
 from misago.acl.models import Role
 from misago.acl.models import Role
 from misago.acl.testutils import fake_post_data
 from misago.acl.testutils import fake_post_data
 from misago.admin.testutils import AdminTestCase
 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.shortcuts import redirect
 from django.utils.translation import ugettext_lazy as _
 from django.utils.translation import ugettext_lazy as _
 from misago.admin.views import generic
 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.acl.views import RoleAdmin, RolesList
 from misago.forums.forms import ForumRoleForm, RoleForumACLFormFactory
 from misago.forums.forms import ForumRoleForm, RoleForumACLFormFactory
 from misago.forums.models import Forum, ForumRole, RoleForumACL
 from misago.forums.models import Forum, ForumRole, RoleForumACL
@@ -24,10 +25,10 @@ class RoleFormMixin(object):
         role_permissions = target.permissions
         role_permissions = target.permissions
         form = ForumRoleForm(instance=target)
         form = ForumRoleForm(instance=target)
 
 
-        perms_forms = get_change_permissions_forms(target)
+        perms_forms = get_permissions_forms(target)
 
 
         if request.method == 'POST':
         if request.method == 'POST':
-            perms_forms = get_change_permissions_forms(target, request.POST)
+            perms_forms = get_permissions_forms(target, request.POST)
             valid_forms = 0
             valid_forms = 0
             for permissions_form in perms_forms:
             for permissions_form in perms_forms:
                 if permissions_form.is_valid():
                 if permissions_form.is_valid():
@@ -49,8 +50,6 @@ class RoleFormMixin(object):
                     return redirect(request.path)
                     return redirect(request.path)
                 else:
                 else:
                     return redirect(self.root_link)
                     return redirect(self.root_link)
-        else:
-            perms_forms = get_change_permissions_forms(target)
 
 
         return self.render(
         return self.render(
             request,
             request,

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

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

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

@@ -12,6 +12,9 @@ DEFAULT_PERMISSIONS = {
 }
 }
 
 
 
 
+"""
+Admin Permissions Form
+"""
 class PermissionsForm(forms.Form):
 class PermissionsForm(forms.Form):
     legend = _("Account settings")
     legend = _("Account settings")
     name_changes_allowed = forms.IntegerField(
     name_changes_allowed = forms.IntegerField(
@@ -34,7 +37,14 @@ class PermissionsForm(forms.Form):
 
 
 
 
 def change_permissions_form(role):
 def change_permissions_form(role):
-    if role.__class__ == Role:
+    if isinstance(role, Role):
         return PermissionsForm
         return PermissionsForm
     else:
     else:
         return None
         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):
 class PermissionsForm(forms.Form):
     legend = _("Destroying user accounts")
     legend = _("Destroying user accounts")
     can_destroy_user_newer_than = forms.IntegerField(
     can_destroy_user_newer_than = forms.IntegerField(
@@ -22,7 +25,14 @@ class PermissionsForm(forms.Form):
 
 
 
 
 def change_permissions_form(role):
 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
         return PermissionsForm
     else:
     else:
         return None
         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):
 class PermissionsForm(forms.Form):
     legend = _("User profiles")
     legend = _("User profiles")
     can_search_users = forms.YesNoSwitch(
     can_search_users = forms.YesNoSwitch(
@@ -24,7 +27,14 @@ class PermissionsForm(forms.Form):
 
 
 
 
 def change_permissions_form(role):
 def change_permissions_form(role):
-    if role.__class__ == Role:
+    if isinstance(role, Role):
         return PermissionsForm
         return PermissionsForm
     else:
     else:
         return None
         return None
+
+
+"""
+ACL Builder
+"""
+def build_acl(acl, roles):
+    pass