asthasr
asthasr

Reputation: 9397

Custom Permissions in Django with Problematic Model

My current project involves a legacy codebase which makes use of Django's models in a limited way; syncdb isn't being used (i.e., the model is not Django-managed). I need to restrict access to certain columns based on permissions (i.e. a view_all permission will show all of the columns, while no permission will restrict the user to a few basic columns). This permission will apply to different tables.

The way I am thinking of doing this is to simply use SQL to insert a new auth_permission. However, this is complicated by the content_type_id column: my understanding is that a content type applies to one model, and this (as I said) will need to apply to different tables, and I can't reliably run syncdb.

Has anyone else implemented something along these lines? Did you use the Django infrastructure, or did you end up using a separate table for safety? Did you implement this at the signal level, or at each point where the model is used?

Thanks!

Upvotes: 1

Views: 557

Answers (4)

Maccesch
Maccesch

Reputation: 2128

It sounds to me like you are actually looking for permission groups. They can be easily created model independently. The user groups wouldn't need to contain any actual permissions.

You can check if a user is in a specific group:

def user_in_group(user, group_name):
    return user.groups.filter(name=group_name).count()

You could use it like this:

def my_view(request):
    if user_in_group(request.user, 'view_all'):
        # do the things
    else:
        # do the other things

# limit this view to 'view_all' users
@user_passes_test(lambda u: user_in_group(u, 'view_all'))
def my_other_view(request):
    # and do some more things

I don't know what you mean with

Did you implement this at the signal level, or at each point where the model is used?

Obviously with any authorization method you would need to insert checks wherever a model is viewed and insert logic to show only the permitted columns (like rendering a subset of columns in a template).

To make sure that no wrong columns are accessed you could write proxy classes that limit access to the apropriate fields/columns and use proxy instances instead of model instances.

class SomeModelProxy(object):
    def __init__(self, model_instance, user):
        self.instance = model_instance
        self.user = user

    def save(self, *args, **kwargs):
        self.instance.save(*args, **kwargs)

    # define other methods that are needed...

    def __getattr__(self, name):
        if not name in get_allowed_columns_for_user_somehow(self.user):
            raise AttributeError

        return getattr(self.instance, name)

    def __setattr__(self, name, value):
        if not name in get_allowed_columns_for_user_somehow(self.user):
            raise AttributeError

        setattr(self.instance, name, value)

Upvotes: 2

rewritten
rewritten

Reputation: 16435

If I understand correctly, you need to show all/partial objects for any table, depending on some property of the user/consumer. A user won't have partial access to some rows and full access to other rows, it's a all-or-nothing.

This has nothing to do with the ContentTypes frameworks, which allows polymorphic relationships at the row level. You just need to filter the exposed fields in the view.

If you use the standard user/group classes, you can add a permission for each table, "see_complete_xxx", and assign groups, then in the view (which being an api will probably loop over the fields and convert them to json/xml/whatever) serialize only the allowed fields for the group.

Something in the line of:

from django.forms.models import model_to_dict

class MyModel(models.Model):

    def serialize_full(self):
        return model_to_dict(self)

    def serialize_restricted(self):
        return model_to_dict(self, self.low_level_access_fields)

    def serialize_to_dict(self, user):
        if self.user_can_view_all(user):
            return self.serialize_full
        else:
            return self.serialize_restricted

    @classmethod
    def user_can_view_all(cls, user)
        return user.has_perm('my_model.see_all')

    @classmethod
    def low_level_access_fields(cls)
        return ["id", "name", "nickname"]

And you can extract most of this code into a mixin and use it where needed.

Upvotes: 0

marianobianchi
marianobianchi

Reputation: 8488

Maybe this can help you. I've never used it but i think you may use it.

You can create some permissions for a group of users and then show some columns or others depending on who is requesting that page/template (with a simple "if" template tag). It can be a little tedious, but it could help you to acomplish what you are looking for.

Hope it helps!

Upvotes: 0

Tisho
Tisho

Reputation: 8482

You can always create custom permissions around a dummy object, i.e.:

class ColumnLevelPermissions(models.Model):

    class Meta:
        permissions = (
             ("can_view_column1", "Can view column 1"),
             ("can_view_column2", "Can view column 2"),
        )

then create a wrapper to filter your database call(you say your models are not managed by Django models.Model):

class SelectManager(object):
    def get_columns(user):
        allowed_columns = []
        if user.has_perm('app.can_view_column1'):
            allowed_columns.append('column1')
        return allowed_columns

The only thing to decide is how to use this. You can create your objects from Database as dict(), and just to filter the columns in the object by key if appear in allowed_columns. This will mean that you will however read some redundant data from the database. Another solution is to modify the "Select " query, based on the columns, although this is not so secure approach.

Some more info on how the objects are stored and read in the code could be useful for defining the best approach.

Upvotes: 2

Related Questions