Code


Version 5 (modified by adrian, 8 years ago) (diff)

Made some edits

Row-level permissions model

The row-level permissions system uses generic relations to decrease the number of tables to only one. The generic relations allow us to create row-level permissions with any instance of any model and connect it to an "owner" object. To allow more freedom, the owner object is also a generic relation. This means that you can use your own models as an owner of a row-level permission. The actual model code is below:

class RowLevelPermission(models.Model):
    model_id = models.PositiveIntegerField("'Type' ID")
    model_ct = models.ForeignKey(ContentType, verbose_name="'Type' content model", related_name="model_ct")
    owner_id = models.PositiveIntegerField("'Owner' ID")
    owner_ct = models.ForeignKey(ContentType, verbose_name="'Owner' content model", related_name="owner_ct")
    negative = models.BooleanField()
    permission = models.ForeignKey(Permission)
    
    model = models.GenericForeignKey(fk_field='model_id', ct_field='model_ct')
    owner = models.GenericForeignKey(fk_field='owner_id', ct_field='owner_ct')
    
    objects = RowLevelPermissionManager()
    
    class Meta:
        verbose_name = _('row level permission')
        verbose_name_plural = _('row level permissions')
        unique_together = (('model_ct', 'model_id', 'owner_id', 'owner_ct', 'permission'),)        

This does not modify the current permissions table at all, and can be layered on top of it if the developer wishes to but it can be ignored easily.

How row permissions are enabled

Row permissions are enabled using the meta class, please see RowLevelPermissions for more information on how to enable them. How this is done is in django.db.models.base.ModelBase under __new__ is the following snippet:

        if getattr(new_class._meta, 'row_level_permissions', None):
            from django.contrib.auth.models import RowLevelPermission
            gen_rel = django.db.models.GenericRelation(RowLevelPermission, object_id_field="model_id", content_model_field="model_ct")
            new_class.add_to_class("row_level_permissions", gen_rel)

django.db.models.options.Options has been modified to set row_level_permissions as disabled by default.

Owner objects

Currently, you can set up an owner by including the following relation:

row_level_permissions_owned = models.GenericRelation(RowLevelPermission, object_id_field="owner_id", content_model_field="owner_ct", related_name="owner")

I might be changing this around in the near future, but I only expect it to be used a few times, and I don't see a large need to make this a similiar process to the enabling of row-level permissions on objects. Please give feedback if you think otherwise.

Checking of row-level permissions

Checking of RLP are done in the following order: User RLP->Group RLP->User Model Level->Group Model Level. Stopping at the first positive or negative.

The has_perm() method has been modified to now check for row-level permissions and has an optional parameter for a model instance, which is required to check row-level permissions.

    def has_perm(self, perm, object=None):
        "Returns True if the user has the specified permission."
        if not self.is_active:
            return False
        if self.is_superuser:
            return True
        if object and object._meta.row_level_permissions:
            row_level_permission = self.check_row_level_permission(perm, object)
            if row_level_permission is not None:
                return row_level_permission
        return perm in self.get_all_permissions()

The check_row_level_permission checks the user RLPs first and then checks the group RLPs. The user RLPs are determined by using a filter method. The group RLP uses an SQL query that works out to be:

SELECT rlp."negative" 
        FROM "auth_user_groups" ug, "auth_rowlevelpermission" rlp 
        WHERE rlp."owner_id"=ug."group_id" 
        AND ug."user_id"=%s        
        AND rlp."owner_ct_id"=%s
        AND rlp."model_id"=%s
        AND rlp."model_ct_id"=%s
        AND rlp."permission_id"=%s;

Integration into administration application

Being worked on. Will post when it is complete or near enough to have a better idea.

Test cases of row-level permissions

API

Test Case Status
Creating RLP using manager's create methodPass
Creating RLP using manager's default permission create methodPass
Creating RLP using init methodPass
Loading RLPs from objectPass
Loading RLPs from owner(user)Pass
Loading RLPs from owner(group)Pass
Deleting RLPPass
Checking of Permissions(will expand on)Pass
Duplicate RLPs(causes error)Pass
Enabling RLPs-

Admin

Test Case'Status
Creating RLP-
Creating RLP w/ wrong permissions-
Deleting RLP-
Editing RLP-
Duplicate RLPs by creating duplicate-
Duplicate RLPs by editing-