six-0.2.0/0000755000175000017500000000000012155352021011666 5ustar boutilboutilsix-0.2.0/README.markdown0000644000175000017500000001312412155352021014370 0ustar boutilboutil## Six - is a ultra simple authorization gem for ruby! _based on clear ruby it can be used for rails 2 & 3 or any other framework_ ### Installation ```ruby gem install six ``` ### QuickStart 4 steps: 1. create abilities object ```ruby abilites = Six.new ``` 2. create object/class with allowed method - here you'll put conditions to define abilities ```ruby class BookRules def self.allowed(author, book) [:read_book, :edit_book] end end ``` 3. Add object with your rules to abilities ```ruby abilities << BookRules # true ``` 4. Thats all. Now you can check abilites. In difference to CanCan it doesnt use current_user method. you manually pass object & subject. ```ruby abilities.allowed?(@user, :read_book, @book) # true ``` ### Usage with Rails ```ruby # Controller # application_controller.rb class ApplicationController < ActionController::Base protect_from_forgery helper_method :abilities, :can? protected def abilities @abilities ||= Six.new end # simple delegate method for controller & view def can?(object, action, subject) abilities.allowed?(object, action, subject) end end # books_controller.rb class BooksController < ApplicationController before_filter :add_abilities before_filter :load_author def show @book = Book.find(params[:id]) head(404) and return unless can?(:guest, :read_book, @book) end def edit @book = Book.find(params[:id]) head(404) and return unless can?(@author, :edit_book, @book) end protected def add_abilities abilities << Book end def load_author @author = Author.find_by_id(params[:author_id]) end end # Model class Book < ActiveRecord::Base belongs_to :author def self.allowed(object, subject) rules = [] return rules unless book.instance_of?(Book) rules << :read_book if subject.public? rules << :edit_book if object && object.id == subject.author_id rules end end # View link_to 'Edit', edit_book_path(book) if can?(@author, :edit_book, book) ``` ### Ruby Usage ```ruby class BookRules # All authorization works on objects with method 'allowed' # No magic behind the scene # You can put this method to any class or object you want # It should always return array # And be aready to get nil in args def self.allowed(author, book) rules = [] # good practice is to check for object type return rules unless book.instance_of?(Book) rules << :read_book if book.published? rules << :edit_book if book.author?(author) # you are free to write any conditions you need if book.author?(author) && book.is_approved? # ....etc... rules << :publish_book end rules # return array of abilities end end # create abilites object abilites = Six.new # add rules abilities << BookRules # true # thats all - now we can use it! abilities.allowed? guest, :read_book, unpublished_book # false abilities.allowed? guest, :read_book, published_book # true abilities.allowed? guest, :edit_book, book # false abilities.allowed? author, :edit_book, book # true abilities.allowed? guest, :remove_book, book # false ``` ### :initialization ```ruby # simple abilities = Six.new # with rules abilities = Six.new(:book_rules => BookRules) # same as Six.new & add(:bok_rules, BookRules) # with more abilities = Six.new(:book => BookRules, :auth => AuthRules, :managment => ManagerRules) ``` ### Adding rules ```ruby abilities = Six.new # 1. simple (recommended) # but you cant use abilities.use(:book_rules) to # search over book namespace only abilities << BookRules # 2. advanced # now you can use abilities.use(:book_rules) to # search over book namespace only abilities.add(:book_rules, BookRules) ``` ### :allowed? ```ruby abilities = Six.new abilities << BookRules abilities.allowed? @guest, :read_book, @book # true abilities.allowed? @guest, :edit_book, @book # false abilities.allowed? @guest, :rate_book, @book # true abilities.allowed? @guest, [:read_book, :edit_book], @book # false abilities.allowed? @guest, [:read_book, :rate_book], @book # true ### :use ```ruby abilities.add(:book_rules, BookRules) abilities.add(:car_rules, CarRules) abilities.allowed? ... # scan for both BookRules & CarRules & require kind_of check abilities.use(:book_rules) abilities.allowed? ... # use rules from BookRules only -> more perfomance ``` ### Namespaces ```ruby class BookRules def self.allowed(author, book) [:read_book, :edit_book, :publish_book] end end class CarRules def self.allowed(driver, car) [:drive, :sell] end end # init object abilities = Six.new # add packs with namespace support abilities.add(:book, BookRules) # true abilities.add(:car, CarRules) # true abilities.add(:ufo, nil) # false abilities.add!(:ufo, nil) # raise Six::InvalidPackPassed # use specific pack for rules (namespace) abilities.use(:book) # true abilities.allowed? :anyone, :read_book, book # true abilities.allowed? :anyone, :drive, car # false abilities.use(:car) abilities.allowed? :anyone, :drive, :any # true abilities.allowed? :anyone, :read_book, :any # false # use reset to return to global usage abilities.reset_use abilities.allowed? :anyone, :drive, :any # true abilities.allowed? :anyone, :read_book, :any # true # different use methods abilities.use(:ufo) # false abilities.use!(:ufo) # raise Six::NoPackError # remove pack abilities.remove(:book) # true abilities.remove(:ufo) # false abilities.remove!(:ufo) # raise Six::NoPackError abilities.use(:car) # true abilities.current_rule_pack # :car ``` six-0.2.0/VERSION0000644000175000017500000000000612155352021012732 0ustar boutilboutil0.2.0 six-0.2.0/Gemfile0000644000175000017500000000024012155352021013155 0ustar boutilboutilsource "http://rubygems.org" group :development do gem 'rspec' gem 'autotest' gem 'ruby-debug19', :require => 'ruby-debug' gem 'awesome_print' end six-0.2.0/six.gemspec0000644000175000017500000000055312155352021014041 0ustar boutilboutilGem::Specification.new do |s| s.name = 'six' s.version = '0.2.0' s.date = '2011-09-02' s.summary = "six" s.description = "Very simple authorization gem" s.authors = ["Dmitriy Zaporozhets"] s.email = 'dmitriy.zaporozhets@gmail.com' s.files = ["lib/six.rb"] s.homepage = 'https://github.com/randx/six' end six-0.2.0/Gemfile.lock0000644000175000017500000000155412155352021014115 0ustar boutilboutilGEM remote: http://rubygems.org/ specs: ZenTest (4.6.1) archive-tar-minitar (0.5.2) autotest (4.4.6) ZenTest (>= 4.4.1) awesome_print (0.4.0) columnize (0.3.4) diff-lcs (1.1.2) linecache19 (0.5.12) ruby_core_source (>= 0.1.4) rspec (2.6.0) rspec-core (~> 2.6.0) rspec-expectations (~> 2.6.0) rspec-mocks (~> 2.6.0) rspec-core (2.6.4) rspec-expectations (2.6.0) diff-lcs (~> 1.1.2) rspec-mocks (2.6.0) ruby-debug-base19 (0.11.25) columnize (>= 0.3.1) linecache19 (>= 0.5.11) ruby_core_source (>= 0.1.4) ruby-debug19 (0.11.6) columnize (>= 0.3.1) linecache19 (>= 0.5.11) ruby-debug-base19 (>= 0.11.19) ruby_core_source (0.1.5) archive-tar-minitar (>= 0.5.2) PLATFORMS ruby DEPENDENCIES autotest awesome_print rspec ruby-debug19 six-0.2.0/LICENSE0000644000175000017500000000204712155352021012676 0ustar boutilboutilCopyright (c) 2011 Dmitriy Zaporozhets Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. six-0.2.0/CHANGELOG0000644000175000017500000000175412155352021013107 0ustar boutilboutilv 0.2.0 - '<<' method for quick pack add w/o specifing namespace Ex. Six.new << BookRules.new - allowed? method can accept array of actions and return true only if all granted Ex. allowed?(:author, [:read, :write, :publish], :book ) # true Ex. allowed?(:author, [:read, :write, :publish, :admin], :book ) # false v 0.1.3 - initialization with pack as args Ex. Six.new(:book => BookRules.new) - initialization with multiple packs Ex. Six.new(:book => BookRules.new, :author => AuthorRules.new) v 0.1.2 - allowed?(action, object, subject) -> allowed?(object, action, subject) - use & use! are now aliases for use_pack & use_pack! v 0.1.1 - 'add' - alias method for add_pack - 'add!' - alias method for add_pack! - 'remove' - alias method for remove_pack - 'remove!' - alias method for remove_pack! - 'reset' - alias method for reset_use - 'exist?' - alias method for pack_exist? - attr_accessor -> attr_reader for current_rule_pack, rules_packs six-0.2.0/.rspec0000644000175000017500000000001012155352021012772 0ustar boutilboutil--color six-0.2.0/lib/0000755000175000017500000000000012155352021012434 5ustar boutilboutilsix-0.2.0/lib/six.rb0000644000175000017500000001120512155352021013563 0ustar boutilboutilclass Six class NoPackError < StandardError def message "No such pack" end end class InvalidPackPassed < StandardError def message "Wrong Rule Pack. You must provide correct 'allowed' method" end end class InitializeArgumentError < StandardError def message "Six.new require hash as pack argument in format {:name_of_pack => PackRules.new}" end end attr_reader :rules_packs attr_reader :current_rule_pack # Initialize ability object # # == Parameters: # packs:: # A Hash or rules to add with initializtion # # == Returns: # self # def initialize(packs={}) raise InitializeArgumentError.new unless packs.kind_of?(Hash) @rules_packs = {} @current_rule_pack = nil packs.each { |key, pack| add_pack!(key, pack) } end # Set current pack from stored packs by key # # == Parameters: # name:: # A Symbol declaring the key name of stored pack # # == Returns: # self or false # def use_pack(name) if pack_exist?(name) @current_rule_pack = name.to_sym self end end # Same as use but raise exception if no pack found def use_pack!(name) use_pack(name) ? self : raise_no_such_pack end # Add pack to authorization class # # == Parameters: # name:: # A Symbol declaring the key name of stored pack # pack:: # Any kind of object responding to allowed method # # == Returns: # true or false # def add_pack(name, pack) rules_packs[name.to_sym] = pack if valid_rules_object?(pack) end # Same as add_pack but raise exception if pack is invalid def add_pack!(name, pack) add_pack(name, pack) || raise_incorrect_pack_object end # Add pack to authorization class w/o key # # == Parameters: # pack:: # Any kind of object responding to allowed method # # == Returns: # true or raise exception # def <<(pack) add_pack!(pack.object_id.to_s, pack) end # Remove pack from authorization class # # == Parameters: # name:: # A Symbol declaring the key name of stored pack # # == Returns: # true or false # def remove_pack(name) if pack_exist?(name) @current_rule_pack = nil if rules_packs[name.to_sym] == @current_rule_pack rules_packs.delete(name.to_sym) end end # Same as remove_pack but raise exception if pack wasnt found def remove_pack!(name) remove_pack(name) || raise_no_such_pack end # Check if object for rule pack is valid # # == Parameters: # pack:: # Any kind of object responding to allowed method # # == Returns: # true or false # def valid_rules_object?(object) object.respond_to?(:allowed) && object.send(:allowed, nil, nil).kind_of?(Array) rescue false end # Check if authorization class has pack with such name # # == Parameters: # name:: # A Symbol declaring the key name of stored pack # # == Returns: # true or false # def pack_exist?(name) rules_packs.has_key?(name.to_sym) end # Check if authorization class allow access for object to subject # using selected pack or all stored. # Basically this method # 1. send :allowed for every stored object in packs and pass object & subject # 2. check if any of results include allowed action # # == Parameters: # action:: # Action name to check for access # object:: # object trying to access resource # subject:: # resource # # == Returns: # true or false # def allowed?(object, actions, subject) # if multiple actions passed # check all actions to be allowed if actions.respond_to?(:each) actions.all? { |action| action_included?(object, action, subject) } else # single action check action_included?(object, actions, subject) end end # Reset current used rule pack so auth class use # global allowed? for new request def reset_use @current_rule_pack = nil end protected def action_included?(object, action, subject) if current_rule_pack rules_packs[current_rule_pack].allowed(object, subject).include?(action) else rules_packs.values.map { |rp| rp.allowed(object, subject) }.flatten.include?(action) end end def raise_no_such_pack raise Six::NoPackError.new end def raise_incorrect_pack_object raise Six::InvalidPackPassed.new end # shotcuts for long methods alias_method :use, :use_pack alias_method :use!, :use_pack! alias_method :add, :add_pack alias_method :add!, :add_pack! alias_method :remove, :remove_pack alias_method :remove!, :remove_pack! alias_method :reset, :reset_use alias_method :exist?, :pack_exist? end six-0.2.0/spec/0000755000175000017500000000000012155352021012620 5ustar boutilboutilsix-0.2.0/spec/six_rules_packs_spec.rb0000644000175000017500000000613312155352021017360 0ustar boutilboutilrequire "./spec/spec_helper" require "./lib/six" describe Six do # define abilities object let (:abilities) { Six.new } describe "Rules Packs" do let(:rules) { BookRules.new } describe "<<" do it { (abilities << rules).should be_true } it { lambda { abilities << nil }.should raise_error(Six::InvalidPackPassed) } it_should_behave_like :valid_abilities do let (:abilities) { Six.new } let (:rules) { BookRules.new } let (:rules_key) { rules.object_id.to_s } before { abilities << rules } end end describe :add do it { abilities.add(:global, rules).should be_true } it { abilities.add(:wrong, nil).should be_false } end describe :add! do it { abilities.add!(:global, rules).should be_true } it { lambda { abilities.add!(:wrong, nil)}.should raise_error(Six::InvalidPackPassed) } end describe "namespace(pack) usage" do before { abilities.add(:global, rules) } describe :use do before { abilities.use(:global) } it "should return class object itself when use existing pack" do abilities.use(:global).should == abilities end describe "should set current role pack with selected" do it { abilities.current_rule_pack.should == :global } end it "should return false when trying to use unexisting pack" do abilities.use(:noname).should be_false end end describe :use! do it "should not raise error if trying to use existing pack" do lambda { abilities.use!(:global)}.should_not raise_error end it "should raise error if trying to use unexisting pack" do lambda { abilities.use!(:noname)}.should raise_error(Six::NoPackError) end end end describe :reset_use do before do abilities.use(:global) abilities.reset_use end it "should set current rule pack variable as nil" do abilities.current_rule_pack.should be_nil end end context "removing pack" do before { abilities.add(:global, rules) } describe :remove do it { abilities.remove(:global).should be_true } it { abilities.remove(:zzz).should be_false } end describe :remove! do it { abilities.remove!(:global).should be_true } it { lambda { abilities.remove!(:zzz)}.should raise_error(Six::NoPackError) } end end describe :valid_rules_object? do let (:invalid_with_allowed) do Class.new { def allowed; nil; end }.new end let (:invalid_wo_allowed) do Object.new end it { abilities.valid_rules_object?(BookRules.new).should be_true } it { abilities.valid_rules_object?(invalid_with_allowed).should be_false } it { abilities.valid_rules_object?(invalid_wo_allowed).should be_false } end describe :pack_exist? do before { abilities.add(:global, rules) } it { abilities.pack_exist?(:global).should be_true } it { abilities.pack_exist?(:ufo).should be_false } end end end six-0.2.0/spec/six_initialize_spec.rb0000644000175000017500000000202612155352021017203 0ustar boutilboutilrequire "./spec/spec_helper" require "./lib/six" describe Six, "initialize" do describe "initalization" do before do @jim = Author.new("Jim") @mike = Author.new("Mike") @jims_book = Book.new("The Game", @jim) @mikes_book = Book.new("Life", @mike) end it "should create authorization object" do Six.new.should be_kind_of(Six) end it "should raise error if invalid argument passed" do lambda { Six.new("wrong argument") }.should raise_error Six::InitializeArgumentError end it "should create authorization object" do Six.new(:book_rules => BookRules.new).should be_kind_of(Six) end it "should create authorization object" do Six.new(:book0 => BookRules.new, :book1 => BookRules.new).should be_kind_of(Six) end describe "passing rules on initialization" do it_should_behave_like :valid_abilities do let(:abilities) { Six.new(:book_rules => BookRules.new) } let(:rules_key) { :book_rules } end end end end six-0.2.0/spec/six_spec.rb0000644000175000017500000000042412155352021014762 0ustar boutilboutilrequire "./spec/spec_helper" require "./lib/six" describe Six do it_should_behave_like :valid_abilities do let (:abilities) { Six.new } let (:rules) { BookRules.new } let (:rules_key) { :book_rules } before { abilities.add(:book_rules, rules) } end end six-0.2.0/spec/spec_helper.rb0000644000175000017500000000022712155352021015437 0ustar boutilboutilrequire "rubygems" require "bundler" Bundler.require(:default, :development) Dir[File.dirname(__FILE__) + '/support/*.rb'].each {|file| require file } six-0.2.0/spec/support/0000755000175000017500000000000012155352021014334 5ustar boutilboutilsix-0.2.0/spec/support/valid_abilities_example.rb0000644000175000017500000000442612155352021021526 0ustar boutilboutilshared_examples :valid_abilities do describe :allowed? do before do @jim = Author.new("Jim") @mike = Author.new("Mike") @jims_book = Book.new("The Game", @jim) @mikes_book = Book.new("Life", @mike) end def allowed?(action, object, subject) # reset use abilities.reset_use # validate work of both global & local namespaces abilities.allowed?(action, object, subject) && abilities.use(rules_key).allowed?(action, object, subject) end describe "should return true or false depend on access" do context :read_book do it { allowed?(@jim, :read_book, @jims_book).should be_true } it { allowed?(@mike, :read_book, @mikes_book).should be_true } it { allowed?(@jim, :read_book, @mikes_book).should be_true } it { allowed?(@mike, :read_book, @jims_book).should be_true } end context :rate_book do it { allowed?(@jim, :rate_book, @jims_book).should be_false } it { allowed?(@mike, :rate_book, @mikes_book).should be_false } it { allowed?(@jim, :rate_book, @mikes_book).should be_true } it { allowed?(@mike, :rate_book, @jims_book).should be_true } end context :edit_book do it { allowed?(@jim, :edit_book, @jims_book).should be_true } it { allowed?(@mike,:edit_book, @mikes_book).should be_true } it { allowed?(@jim, :edit_book, @mikes_book).should be_false } it { allowed?(@mike,:edit_book, @jims_book).should be_false } end context :publish_book do it { allowed?(@jim, :publish_book, @jims_book).should be_false } it { allowed?(@mike,:publish_book, @mikes_book).should be_false } it { allowed?(@jim, :publish_book, @mikes_book).should be_false } it { allowed?(@mike,:publish_book, @jims_book).should be_false } end context 'passing multiple actions' do it { allowed?(@jim, [:read_book, :edit_book], @jims_book).should be_true } it { allowed?(@jim, [:ead_book, :publish_book, :edit_book], @jims_book).should be_false } it { allowed?(@mike, [:read_book, :edit_book], @mikes_book).should be_true } it { allowed?(@mike, [:rate_book, :publish_book, :edit_book], @mikes_book).should be_false } end end end end six-0.2.0/spec/support/author.rb0000644000175000017500000000012612155352021016162 0ustar boutilboutilclass Author attr_accessor :name def initialize(name) @name = name end end six-0.2.0/spec/support/book_rules.rb0000644000175000017500000000066312155352021017032 0ustar boutilboutilclass BookRules def allowed(author, book) # check for correct class & nil return [] unless author.instance_of?(Author) && book.instance_of?(Book) rules = [] rules << :read_book if book.public || book.author?(author) rules << :rate_book if book.public && !book.author?(author) rules << :edit_book if book.author?(author) rules << :publish_book if book.author?(author) && !book.public rules end end six-0.2.0/spec/support/book.rb0000644000175000017500000000033012155352021015607 0ustar boutilboutilclass Book attr_accessor :name, :public, :author def initialize(name, author, is_public = true) @name, @author, @public = name, author, is_public end def author?(author) @author == author end end six-0.2.0/.gitignore0000644000175000017500000000000712155352021013653 0ustar boutilboutil.rvmrc