Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Authorization plugin for use with merb-auth
branch: master

This branch is even with hassox:master

Fetching latest commit…

Cannot retrieve the latest commit at this time

Failed to load latest commit information.
lib
spec
.gitignore
LICENSE
README.textile
Rakefile
TODO

README.textile

Experimental.. Don’t use I’ll be tearing this one up because it sucks

MerbAuthz – Authorization

Authorization in MerbAuthz is a high level api for authorization in Merb applications. It
does not implement any particular authorization scheme, but rather provides a mechanism
for you to specify your own authorization logic and apply it in a very granular fashion.

Authorization is different to authentication in that authentication asks who a user is,
makes sure they’re valid on the system. Authorization on the other hand asks the question
“can the user do this”.

When using MerbAuth, the api inside an app can remain the same while what it means to be authorized can
completely change underneath. This approach provides many benefits. Apps can share
the same api with wildly different meanings underneath where the application developer does
not have to know intricately how the authorization is implemented.

Note: I’ll just show usage throughout the readme with policies that are assumed to exist except where showing policy implementation

Autorizable Objects (Operators)

Any kind of object can be made authorizable, meaning that it can be asked “Can this operator do this?”
Typically, an authorizable object is a User, but it could also be a Group, Application, Machine or
anything that makes sense to ask is this operator authorized to…

You make an object authorizable by using the authorizable! method.


  User.authorizable!
  Group.authorizable!

This will add the behavior required for this object to be asked if they are authorized.

Checking Authorization

Once you’ve declared a class as authorizable!, you can ask instances of that class if they’re authorized?


  @user.authorized?(:admin)
  @user.authorized?(:publisher)
  @user.authorized?(:read, :target => @article)

There are two types of authorization available.

  1. General authorization, where the question is asked of the operator object alone. For example, this could be asking if the
    user is an administrator. For example, for the following api call:
    
        @user.authorized?(:admin)
      

    the underlying logic could look like this:
    
        @user.has_role?('admin')
  2. or
    @user.admin?
  3. or even
    @user.login == “Me!”

    The logic is focused purely on the operator object. This kind of authorization doesn’t focus on any particular object, it’s just a general question about the operator.

  4. Instance authorization. This is where you’re asking an instance if an operator has some relationship. For example for this api call:
    
        @user.authorized?(:publish, :target => @article)
      

    The underlying logic could look like:
    
        @user.can_publish?(@article)
  5. or
    @user.has_role?(:publisher) && @article.publishable?
  6. or even just
    @article.owner == @user

    With instance level authorization, you’re wanting to find out if an operator is authorized with respect to some target object.

The way you differentiate between the two types in the api is by supplying the :target option to the authorized? method.

How Authorization is Implemented

Authorization is implemented in MerbAuth by using Policies. A policy is a class that contains the logic for a given
authorization scheme. You group these policies by using a policy group label either in a global scope, or scoped to a
particular class. For example, you may add the “Admin”, and “Publisher” policies to the global :publisher label. Then when you
ask user.authorized?(:publisher) it will try each one until either one passes, or all fail.

Policy Group Labels

A policy group label is a symbol that is used to identify a particular group of policies. There can be one, or many policies for a
given group. You can apply policy group labels in a global scope, scoped to a class, copied from a class and even inherited.
You can define policy groups on any object that inherits from Object. This allows you to have fine control over the policy groups
used when checking authentication.

Global Policy Labels

Mapping a policy group label to a group of policies in the global scope is simple.


  Merb::Authorization.global_policies do
    for_label( :admin    ).use_policies("Admin")
    for_labels(:publisher).use_policies("Publisher", :admin)
  end
</pre>

Once you’ve done that you’re in business. This sets up global policies that you can use anywhere in your application when using
general or instance authorization checks.

Notice the :admin label in the policies list for the :publisher group. This will copy the policies from the :admin label
that you setup before. You can build a complex policy groups in this way.


  do_something_big if operator.authorized?(:admin)
  show_publish_button if operator.authorized?(:publisher)

There’s singular and plural versions of both for_labels and use_policies

Setting Instance Policies

When you want to set instance policies, you do it just the same as when you’re setting up global policies.


  class Article
    include DataMapper::Resource
    
    authorization do
      for_labels(:create          ).use_policy("Public")
      for_labels(:update, :edit   ).use_policy("Owner", :publisher, :admin)
      for_labels(:read,   :view   ).use_policy("Published", :update)
      for_labels(:delete, :destroy).use_policies("Owner", :admin)
    end
    
    # snip
  end

From this example all crud actions are setup on Article ready to be protected. The labels can be anything you like, and as is
shown in the above example, you can copy from the global policy groups.

With the above definition you could do:


  @user.authorized?(:read,   :target => @article)
  @user.authorized?(:update, :target => @article)
  @user.authroized?(:create, :target => Article)

These will use the policy groups you’ve setup on Article. Note that if you pass a class as :target that will
use the policy group labels found on the class, and execute the check as a general policy, not an instance one.

Copying Policy Groups Across Classes

Sometimes you may have policy groups that are very similar or even the same that you want to re-use elsewhere. One option is
to put a group in the global space and then you can use those labels in your class authorization definition. You can
also copy directly from another class.


  class News
    authorization do
      with_scope(Article) do
        for_label( :create       ).use_policy(:create)
        for_labels(:read, :view  ).use_policy(:read)
        for_labels(:update       ).use_policy(:update, "OwningDepartment")
        for_labels(:delete       ).use_policy(:delete, "OwningDepartmentHead")
      end
    end
  end

This will copy the policies from the ones on Article and add any you’ve added.

Inheriting Policies

Policies are inheritable. In the child, you can add to, replace or clear the policies inherited from the parent. For example:


  class Tutorial < Article
    authorization do
      for_label(:edit, :update).add_policies("OwningDepartment")
      for_label(:create).clear!
      for_label(:delete).use_policies(:admin, "Owner")
    end
  end

In this example, the add_policies will add the new policies to the inherited ones. clear! will remove the policy group
for that label, and use_policies will overwrite the inherited policy group.

Authorizing Controllers

All this is well and good but a lot of the time in merb is spent inside the controller. Controllers need to be easy to
authorize. Here’s an example of authorization of the “Articles” controller:

class Articles < Application authorization(Article) do for_action(:index ).use_label(:read) for_actions(:create ).use_label(:create) with_target(:find_member) do for_action(:show ).use_label(:read) for_actions(:edit, :update ).use_label(:update) for_actions(:delete, :destroy ).use_label(:delete) end end private def find_member @article = Article.get(params[:id]) raise NotFound unless @article @article end end

This could be a typical setup for a CRUD controller that you setup manually. The authorization method on the controller receives an optional parameter
which is used as a default target. Passing it a class will effectively scope the controller to a particular class.

In the above example you can see each method being protected with a label. The labels can be for the Global policies or
for the scoping class passed to authorization.

The authorization will be checked before the action is called. If there is no user logged in, ensure_authenticated will
be called to try a login. If there is no login it will raise an Unauthenticated action as per Authentication.

If the authorization fails, an Unauthorized exception will be raised.

with_target

The with_target helper in the above example scopes each of the actions in the block to use a target object. Pass in a symbol
to have it call a method on the controller (as above) or pass it a proc.

This will call the method / proc and use the result as the target object. For example, in the above example for the show action
it will end up as:


  session.user.authorized?(:read, :target => @article)

For the index and create actions however, which aren’t in the with_target block, the target will be the scoped class passed to the authorization method. It will boil down to:


  session.user.authorized?(:read, :target => Article)

Protecting CRUD Resources

There is a special helper for protecting CRUD resources. Lets just see it


  class MyCrudController < Application
    authorize_crud_resource Article
    
    # snip
    private 
    def find_member
      Article.get(params[:id])
    end
  end

This will protect all normal crud methods on MyCrudController with the Article model. That is: create, new, index, show, edit,
update, delete, and destroy. To have this work, you need to setup the :create, :read, :update, and :delete labels on
the Article model.

create, new, and index are evaluated in the general sense, and the rest are evaluated with a target instance.

You can also add custom actions.


  class MyCrudController < Application
    authorize_crud_resource Article do
      for_action(:custom).use_label(:admin)
    end
    
    def custom
      # stuff
    end
    
    # snip
  end

Defining Policies

Throughout the README I’ve just been using policies like they’re there. Lets take a look at what a policy actually is though.

A Policy is a class. It can be executed in two contexts.

  1. general – The general sense of a policy is a bare question of the operator. Like, are you an admin, are you a publisher etc.
  2. instance – The instance sense of a policy is focused on a particular object and how the operator relates to it. You can ask questions like, “are you the owner of foo?”

At it’s heart, the sense of the policy is governed by the :target option in the authorized? method. When you ask user.authorized?(:read, :target => @article) the policy will be executed in the instance sense. When you don’t pass
:target or you pass :target a class, it will be executed in the general sense.

Rules for Policies

A policiy is a class. Policies inherit from Merb::Authorization::Policies::Policy and should be declared in the Merb::Authorization::Policies namespace.

A policy can implement two methods. general_policy and instance_policy. These are the methods that are called
on the policy when appropriate. To pass a policy, return true from these methods, to fail, return false. When you fail, any other policies that are in the group are tried until one passes or all fail. By default, if you don’t implement the method (because it’s meaningless in the context of that policy) it will return false for you. Lets take a look at a couple.


  
  module Merb::Authorization::Policies
    class Admin < Policy
      def general_policy(operator, options)
        operator.admin?
      end
      
      def instance_policy(operator, instance, options)
        general_policy(operator, options)
      end
    end
    
    class Owner < Policy
      def instance_policy(operator, instance, options)
        instance.respond_to?(:owner) && instance.owner == operator
      end
    end
    
    class Publisher < Policy
      def general_policy(operator, options)
        operator.publisher?
      end
      
      def instance_policy(operator, instance, options)
        operator.publisher? && instance.draft?
      end
    end
  end
  

Here’s just a few policies that could be declared. You can see that some of them have both methods, and the Owner doesn’t. That’s because Owner doesn’t make sense in a general context, only in an instance context. You can call the other methods inside them,
and also, there’s nothing stopping you from checking other policies inside this one. You could easily call operator.authorized?(:foo) inside the policy.

Using Options in Policies

You can see from the above example that there are options passed to each policy. This allows your policy to behave slightly differently based on the options passed in. For example, say you wanted to scope a policy to a particular department. You could use
user.authorized?(:edit, :target => @article, :department => @department). The @department variable will be made available in options[:department] and you could use it to make the policy decisions.

Note: It is a really bad idea to use anything related to the request object in the policy options hash. If you do, your policy will not work correctly in mailers, models, runners etc where there is no notion of a request. Policies should be stateless

Using Options in the Controller

Sometimes you may need / want to use options when protecting your controller. Here’s an example of doing just that.


  
  class Contacts < Application
    authorization(Contact) do
      with_options(:department_options) do
        for_action(:index).use_label(:read)
        
        with_target(:find_member) do
          for_action( :show               ).use_label(:read)
          for_actions(:edit,    :update   ).use_label(:update)
          for_actions(:delete,  :destroy  ).use_label(:delete)
        end
      end
    end
    
    # snip
    
    private 
    def department_options
      {:department => Department.get(params[:department_id])}
    end
    
    def find_member
      @contact = Contact.get(params[:id])
    end
  end
  

The with_options helper will provide the options found in the department_options method during the request and
pass this to each policy to make use of.

Something went wrong with that request. Please try again.