Someone asked: what is user and they? It was a really good question and deserved a better explanation.
The first thing to consider is @authorization_method This is a method decorator,a really nice Python feature — particularly when you are writing framework code.
A method decorator is a method that takes in a method as an argument and returns a mutated method. (pause to re-read that)
Let’s take a look at this specific implementation:
123456789101112
# in bouncer/__init__.pydefget_authorization_method():return_authorization_method_authorization_method=Nonedefauthorization_method(original_method):"""The method that will be injected into the authorization target to perform authorization"""global_authorization_method_authorization_method=original_methodreturnoriginal_method
So in the instance of our authorization_method, we receive a function and store it in the global variable _authorization_method. We can make of use of this function later in application’s execution.
For example. In my talk I showed the can method:
12345678910
jonathan=User(name='jonathan',admin=False)marc=User(name='marc',admin=False)article=Article(author=jonathan)printcan(jonathan,EDIT,article)# Trueprintcan(marc,EDIT,article)# False# Can Marc view articles in general?printcan(marc,VIEW,Article)# True
can is defined as follows:
12345678910111213
defcan(user,action,subject):"""Checks if a given user has the ability to perform the action on a subject :param user: A user object :param action: an action string, typically 'read', 'edit', 'manage'. Use bouncer.constants for readability :param subject: the resource in question. Either a Class or an instance of a class. Pass the class if you want to know if the user has general access to perform the action on that type of object. Or pass a specific object, if you want to know if the user has the ability to that specific instance :returns: Boolean """ability=Ability(user,get_authorization_method())returnability.can(action,subject)
When “can” is called, it builds an Ability using the logic in method we decorated (stored) with @authorization_method
Having said that, let me explain what they and they.can is.
# in bouncer/models.pyclassRuleList(list):defappend(self,*item_description_or_rule,**kwargs):# Will check it a Rule or a description of a rule# construct a rule if necessary then appendiflen(item_description_or_rule)==1andisinstance(item_description_or_rule[0],Rule):item=item_description_or_rule[0]super(RuleList,self).append(item)else:# try to construct a ruleitem=Rule(True,*item_description_or_rule,**kwargs)super(RuleList,self).append(item)# alias append# so you can do things like this:# @authorization_method# def authorize(user, they):## if user.is_admin:# # self.can_manage(ALL)# they.can(MANAGE, ALL)# else:# they.can(READ, ALL)## def if_author(article):# return article.author == user## they.can(EDIT, Article, if_author)can=append
RuleList is a python list with two tweaks:
override append to handle inputing of Rules or something I can construct into a rule
alias append can = append which allows us to have the desired syntax they.can(READ, ALL)
I am pretty pleased with this; I really like the they.can(READ, ALL) syntax. Some may argue that it is not pythonic since I could be more explicit — but in this case I think ease of readability trumps style.
But if you don’t agree, no worries you can use the following equivalent syntax:
12345678910
@authorization_methoddefauthorize(user,abilities):ifuser.is_admin:abilities.append(MANAGE,ALL)else:abilities.append(READ,ALL)# See I am using a string hereabilities.append(EDIT,'Article',author=user)
Both work!
Hopefully this clarifies things. Feel free to ping me with additional questions.
Addendum
There has been a fair bit of discussion in my office about the grammatically correctness of they. Uncannily, xkcd comes to the rescue once again: