Imagine you’re writing a Rails app to organize conferences. As soon as you know what the app can do, you have to start deciding who can do what. Who is allowed to:
- Decide who will speak at the conference?
- Edit the presenter schedule?
- Upload presentation slides?
- Comment on those slides?
- Create playlists of music?
- Make a personal schedule of which talks to see?
All these questions are about authorization: “what is this user authorized to do?” Obviously, it’s important to get this logic right: it needs to be correct, and it needs to be consistent. And it would be nice if that logic was grouped together, rather than scattered all over your app.
Protecting Your Models
Before I show you how Authority works, let’s talk about some of the general ideas.
Authority’s notion of permissions in your Rails app is focused on your models; in the example above, these would be presenters, comments, playlists, etc. In some cases, the question is very simple: a conference attendee cannot make any changes to any presenter, period. You can think of that as a “class-level” rule: the
Presenter model is read-only for anyone who isn’t a conference organizer. If an attendee tries to visit the page for editing a presenter’s bio, we don’t have to ask any questions about this particular presenter to know that the action is not allowed.
In other cases, the question is more nuanced: attendees can edit their own personal schedule, but not anyone else’s. You can think of that as an “instance-level” rule: to know whether a conference attendee can edit a schedule, you have to look at that schedule instance and see who it belongs to.
Using Authority, you’d use class methods, like
def self.updatable_by?(user) to set class-level rules, and instance methods, like
def deletable_by?(user) , to set instance-level rules.
But where should those methods be written?
Keeping Your Permissions DRY
Obviously, different models have different rules, so you might think that authorization methods should go on the models themselves.
But it’s likely that some of your models share rules: anyone who can edit a
Presenter can also edit the
PresenterSchedule. If that’s true, it would be nice to keep that DRY: let the
Presenter model and the
PresenterSchedule model use the same authorization logic.
Authority accomplishes this by having the model delegate any questions of authorization to a specified Authorizer class. Models with the same rules can point to the same Authorizer.
To take a simple example from the gem’s README, suppose you two have categories of resources in your app: some for regular users and some for administrators. Using Authority, you’d have two authorizer classes. You might call them
You could group your models like this:
BasicAuthorizer AdminAuthorizer+ ++-+ +------++ + +Comment Article Edition
In this example, the
Comment model’s authorization rules come from
Edition get theirs from
AdminAuthorizer. You’d call
self.authorizer_name = on each model to set this up.
AdminAuthorizer might have a method like this:
class AdminAuthorizer < Authority::Authorizerdef self.creatable_by?(user)user.is_admin?endend
Anytime a user tries to create an
Article or an
Edition, this method will be called. If the user isn’t an admin, it will return false and the action will be denied.
Any method that isn’t defined on an authorizer will be inherited from
Authority::Authorizer, which will consult a configurable
default_strategy proc. The built-in default strategy simply returns false. This is a whitelisting approach: any action you don’t explicitly allow will be forbidden. But you can supply your own default strategy to do something more nuanced.
So the full lookup chain looks like this:
default_strategy++--------+-------++ +BasicAuthorizer AdminAuthorizer+ ++-+ +------++ + +Comment Article Edition
Standard Ruby Classes And Methods
The nice thing about this structure is that it’s just regular object-oriented programming. Your authorizers are just classes, so you can modify them any way you like: include modules, change the parent class, metaprogram, etc.
In addition, the authorizer’s methods are just plain Ruby methods: there’s no new syntax to learn. Authority makes no assumptions about what logic you’ll need. You can consult a database or a file or a web service to make your decisions; if you use a database, you can use whatever ORM you like. All the logic is up to you; Authority just helps you keep it organized.
Syntactic Sugar for Users
So far we’ve seen our authorization methods in the passive voice: is this resource creatable by this user? But it’s nice to be able to say the same thing in an active voice: can this user create this resource?
Like several other popular authorization gems, Authority gives you this syntactic sugar.
current_user.can_edit?(@article) is simply a pass-through to
@article.editable_by?(current_user), which in turn would ask the
can_edit? and similar methods are defined on the user object, you can use them anywhere that object is available. A common case would be to show links only to users who should see them:
link_to new_article_path if current_user.can_create?(Article)
A Little Magic for Controllers
If you’re using the method shown above to hide links from unauthorized users, most people won’t try anything they shouldn’t be doing. But what if someone manually types the URL to edit a resource that’s forbidden for them?
At that point, your controller must intervene. Authority gives you a couple of methods for this:
authorize_actions_for(ModelName), which checks class-level permissions and will stop the controller method from ever running, and
authorize_action_for(@model_instance), which checks instance-level permissions from inside a controller method.
In either case, forbidden actions are handled by a controller method that you specify; the default one logs the user’s action and displays a warning.
Check it out
You can get more detail on everything discussed here by looking at the README on Github. I’d also encourage you to read the source code; Authority isn’t a large gem, and the source is well-commented. You may learn something, and of course, reading the code is the first step towards contributing.