daisylb / bridgekeeper

Django permissions, but with QuerySets
https://bridgekeeper.leigh.party/
MIT License
42 stars 14 forks source link

Simpler, more concise rule declarations with R objects #9

Open daisylb opened 6 years ago

daisylb commented 6 years ago

bridgekeeper.rules should expose a R class, with an API similar to django.db.models.Q.

This class would take keyword arguments, with either constants, functions that take a user, or other Bridgekeeper rules as values.

This class should take keyword arguments, where keys are attribute names (traversing relationships with __ where applicable), and values are either database values, functions that take a user, or (where the target attribute is a relationship) another Bridgekeeper rule object.

As an example of what this might look like, here's a couple of snippets from the documentation, alongside the equivalent rule expressed in terms of R.

blue_widgets_only = Attribute('colour', matches='blue')
blue_widgets_only = R(colour='blue')
perms['shrubbery.update_shrubbery'] = Attribute('branch', lambda user: user.profile.branch)
perms['shrubbery.update_shrubbery'] = R(branch=lambda user: user.profile.branch)
perms['shrubbery.update_shrubbery'] = Relation(
    'branch',
    models.Branch,
    # This rule gets checked against the branch object, not the shrubbery
    Attribute('store', lambda user: user.profile.branch.store),
)

perms['shrubbery.update_shrubbery'] = R(branch__store=lambda user: user.profile.branch.store)
perms['foo.view_application'] = Relation(
    'applicant', Applicant, perms['foo.view_applicant'])

perms['foo.view_application'] = R(applicant=perms['foo.view_applicant'])
perms['foo.view_customer'] = ManyRelation(
    'agencies', Agency, Is(lambda user: user.agency))

perms['foo.view_customer'] = R(agencies=lambda user: user.agency)
perms['shrubbery.update_shrubbery'] = is_staff | (
    is_shrubber & Relation(
        'branch',
        models.Branch,
        Attribute('store', lambda user: user.profile.branch.store),
    )
) | (
    is_apprentice & Attribute('branch', lambda user: user.profile.branch)
)

perms['shrubbery.update_shrubbery'] = is_staff | (
    is_shrubber & R(branch__store=lambda user: user.profile.branch.store)
) | (
    is_apprentice & R(branch=lambda user: user.profile.branch)
)
daisylb commented 6 years ago

It is worth pointing out that this work will depend on work for #3, which will also decrease the verbosity of some of the non-R examples a bit.

daisylb commented 6 years ago

As an aside, lookups (__gt, __in etc) could also be supported. This could be done with each one being a rule object of its own that takes a property and a value_or_function, since they'd need to do one thing for Q lookups and another for Python-land checks, and rule objects are already a mechanism for doing that. My intention is not to do that at first though, since we haven't encountered a need for testing anything other than equality yet.