Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Using Rules for attribute level access control? #81

Open
vincentwhales opened this issue Jun 22, 2018 · 5 comments
Open

Using Rules for attribute level access control? #81

vincentwhales opened this issue Jun 22, 2018 · 5 comments

Comments

@vincentwhales
Copy link

vincentwhales commented Jun 22, 2018

In my django application, I have a model, Client, which holds information about the clients of my business. I have two groups of employees that uses my application - Technician and Finance.

Members of Technician can view/update attributes of Client such as 'password_hash', 'login_ip_address'. Members of Finance can view/update attributes such as 'total_revenue' or 'bank_account_number'.

I see in the documentation that Predicates can be created from any callable that accepts anything from zero to two positional arguments.

  • Is it possible to extend this to three positional arguments? I am interested to do the following:
@rules.predicate
def can_view_attribute(user, model, attribute):
  • If the above is not possible, I plan to generate a rule for the Client class for every of its attribute. Rule generation will happen at the initialization phase of my application.
    • Are there any drawbacks to this approach?
    • Has anyone attempted this approach before?
@rules.predicate
def can_view_client_attribute(user, attribute):
@BoPeng
Copy link

BoPeng commented Feb 13, 2020

I am in a similar situation. I have a case where an object can be subscribed to multiple groups and I would like to check if a user can subscribe or unsubscribe object from the groups. The logic is something like

def can_subscribe(user, object, group):
   return is_owner_or_maintainer(user, object) and is_member(user, group) \
        and not is_subscribed(object, group)

is_subscribed is a relationship between object and group and is not a predicate for now. The problem here is that is_owner_or_maintainer does not know which group the object is intended to subscribe, and is_member does not know which object the user is trying to subscribe. So in the end can_subscribe cannot be implemented as a predicate and has to be a function that uses two predicates.

Although django's has_perm mechanism is restricted to has_perm(user, object), perhaps three-way predicator could be added to the general rule sets?

Another possibility is to extend context to allow user-specified context so that the predicate would know which group the predicate is applied. Something like

def can_subscribe(user, object):
    group = can_subscribe.context['group']
    # or group = request.some_way_to_determine.group
   return is_owner_or_maintainer(user, object) and is_member(user, group) \
        and not is_subscribed(object, group)

@BoPeng
Copy link

BoPeng commented Feb 15, 2020

Not quite sure if it would work but maybe the following could solve my problem:

def can_subscribe(user, objects):
   return is_owner_or_maintainer(user, objects[0]) and is_member(user, objects[1]) \
        and not is_subscribed(objects[0], objects[1])

and be used as

user.has_perm('can_subscribe', [object, group])

@simkimsia
Copy link

I have similar needs as @vincentwhales tho I can currently live without this feature for now.

Another related topic is read-access to the property of the Django model. Attributes of a Django model are almost always actual data fields inside the database.

Properties are not always the case. They are kinda like virtual fields. While they cannot be modified (at least I think so), they can be read. Therefore certain read permissions might occur depending on the user's confidentiality and authorization level.

@JackAtOmenApps
Copy link

JackAtOmenApps commented Nov 28, 2021

@vincentwhales I'm just newly reading the docs for django-rules, so I may be missing something, but can you not just combine two simpler predicates to achieve what you want?

I don't know the structure of your models, but assuming you have a one-to-one of either Technician or Finance instance pointing to each User instance, wouldn't the following achieve your goal?

@rules.predicate
def user_is_technician(user):
    return hasattr(user, "technician")

@rules.predicate
def technician_has_attribute(user, attribute):
    return hasattr(user.technician, attribute)

user_is_technician_and_has_attribute = user_is_technician & technician_has_attribute
rules.add_perm('myapp.has_technician_and_attribute', user_is_technician_and_has_attribute)


@rules.predicate
def user_is_finance(user):
    return hasattr(user, "finance")

@rules.predicate
def finance_has_attribute(user, attribute):
    return hasattr(user.finance, attribute)
    
user_is_finance_and_has_attribute = user_is_finance & finance_has_attribute
rules.add_perm('myapp.has_finance_and_attribute', user_is_finance_and_has_attribute)

Or a more general case that doesn't care is user is Technician or Finance in the predicate function definition, because we can get that info from the user instance. We check if user has an associated technician or finance model instance, and then check that for the attribute. Returning False if user does not have an associated technician or finance instance, or if the related instance does not have attribute. This does assume that the attributes do not have overlap between the two client types.

@rules.predicate
def has_attribute(user, attribute):
    
    if hasattr(user, "technician"):
    	return hasattr(user.technician, attribute)
    if hasattr(user, "finance"):
    	return hasattr(user.finance, attribute)
    return False

rules.add_perm('myapp.has_attribute', has_attribute)

jack = User.objects.get(username="jack")
jack.has_attribute("login_ip_address")

There's probably a more optimal solution, and I've only looked at the docs for 20-some minutes, but I would think this would work based on what I've read so far.

(sorry to revive such an old thread. Just surprised nobody has commented with this suggestion before. Or maybe I'm totally misunderstanding something)

@benwhalley
Copy link

I came here to request the same feature. Django inplace edit (https://github.com/ptav/django-inlineedit) has a nice feature of passing the (user, object, field) to a callable that checks permission to edit. It would be nice if this could be passed off to Django-rules predicates. At the moment I can't handle field level edit permissions

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

6 participants