/master-may-i

Super simple model based authorization designed to work with AuthLogic

Primary LanguageRubyMIT LicenseMIT

Master May I?

Clean, simple and flexible model based authorization designed to work with AuthLogic and InheritedResources.

If these docs looks all wanky, then be sure you’re viewing them on the rdoc.info site: rdoc.info/projects/tsaleh/master-may-i

Overview

{MasterMayI Master May I} is not an Access Control List system. In fact, it has absolutely no opinions about how you structure your underlying business rules. All it does is gives you a consistent and flexible pattern for accessing those rules.

There are two main components:

The Model Layer

{MasterMayI Master May I} adds query methods to all your models, which can be used to determine who’s allowed to do what. {MasterMayI::ActiveRecordExtensions Learn more.}

  • {MasterMayI::ActiveRecordExtensions::ClassMethods#creatable_by? +Model.creatable_by?(user)+} and {MasterMayI::ActiveRecordExtensions::ClassMethods#creatable? Model.creatable?}

  • {MasterMayI::ActiveRecordExtensions::InstanceMethods#readable_by? +@model.readable_by?(user)+} and {MasterMayI::ActiveRecordExtensions::InstanceMethods#readable? +@model.readable?+}

  • {MasterMayI::ActiveRecordExtensions::InstanceMethods#editable_by? +@model.editable_by?(user)+} and {MasterMayI::ActiveRecordExtensions::InstanceMethods#editable? +@model.editable?+}

  • {MasterMayI::ActiveRecordExtensions::InstanceMethods#destroyable_by? +@model.destroyable_by?(user)+} and {MasterMayI::ActiveRecordExtensions::InstanceMethods#destroyable? +@model.destroyable?+}

These are just methods (that default to always returning true), so customizing the rules is as easy as overriding the methods:

class Note < ActiveRecord::Base
  def self.creatable_by?(user)
    user and user.administrator?
  end
end

{MasterMayI Master May I} also adds the {MasterMayI::ActiveRecordExtensions::ClassMethods#listable_by +Model.listable_by(user)+} and {MasterMayI::ActiveRecordExtensions::ClassMethods#listable Model.listable} methods, which return a named scope limiting the returned records to those viewable by the given user. Again, by default, it returns all records, and this should be redefined by each individual model.

If you call {MasterMayI::ActiveRecordExtensions::ClassMethods#records_creating_user records_creating_user} in your model, then the user from the Authlogic UserSession is automatically stored whenever a record is created. {MasterMayI::ActiveRecordExtensions::ClassMethods#records_creating_user Learn more.}

class Note < ActiveRecord::Base
  records_creating_user

  def self.creatable_by?(user)
    user and created_by?(user)
  end
end

The Controller Layer

If you include {MasterMayI::ControllerExtensions} in your ApplicationController, MasterMayI adds a few utility methods to your ApplicationController:

  • {MasterMayI::ControllerExtensions#current_user current_user}

  • {MasterMayI::ControllerExtensions#logged_in? logged_in?}

  • {MasterMayI::ControllerExtensions#require_user require_user}

  • {MasterMayI::ControllerExtensions#deny_access deny_access}

  • {MasterMayI::ControllerExtensions#store_location store_location}

  • {MasterMayI::ControllerExtensions#redirect_back_or +redirect_back_or url+}

{MasterMayI Master May I} will also include the {MasterMayI::ControllerExtensions::ClassMethods#protects_restful_actions protects_restful_actions} controller class method, which integrates with InheritedResources in order to automatically check with the model in a set of before filters. In addition, it will override the InheritedResources#collection method to filter by the listable scope. {MasterMayI::ControllerExtensions::ClassMethods#protects_restful_actions Learn more.}

class ApplicationController < ActionController::Base
  include MasterMayI::ControllerExtensions
  ...
end

class NotesController < InheritedResources::Base
  protects_restful_actions
end

{MasterMayI::ControllerExtensions Learn more.}

Testing with Shoulda

{MasterMayI Master May I} comes with a strong set of Shoulda macros:

…for your {ActiveSupport::TestCase unit tests}:

class NoteTest < ActiveSupport::TestCase
  setup :activate_authlogic

  should_record_creating_user :as => "user"

  should_be_creatable_by("boy named sue-create") { Factory(:user, :username => "sue-create")  }
  should_not_be_creatable_by("everyone")         { nil }

  context "a note" do
    setup { @note = Factory(:note) }
    subject { @note }

    should_be_readable_by("boy named sue-read")       { Factory(:user, :username => "sue-read")    }
    should_be_editable_by("boy named sue-edit")       { Factory(:user, :username => "sue-edit")    }
    should_be_destroyable_by("boy named sue-destroy") { Factory(:user, :username => "sue-destroy") }

    should_not_be_readable_by(   "everyone") { nil }
    should_not_be_editable_by(   "everyone") { nil }
    should_not_be_destroyable_by("everyone") { nil }
  end
end

…and your {ActionController::TestCase functional tests}:

class NotesControllerTest < ActionController::TestCase
  setup :activate_authlogic

  as_a_logged_in_user do
    who_can_manage :notes do
      context "on GET to /notes/new" do
        setup { get :new }
        should_not_set_the_flash
        should_render_template :new
      end
      ...

Installation

{MasterMayI Master May I} is hosted on gemcutter, so installation is as easy as:

sudo gem install master_may_i

It relies heavily on Authlogic, and the controller module uses the resource and resource_class methods from InheritedResources. Finally, the before filters expect there to be the a url helper named login_url:

map.login "/login", :controller => :user_session, :action => :new

Copyright © 2009 Tammer Saleh. See LICENSE for details.