thwart 0.0.0

Sign up to get free protection for your applications and to get access to all the features.
data/.document ADDED
@@ -0,0 +1,5 @@
1
+ README.rdoc
2
+ lib/**/*.rb
3
+ bin/*
4
+ features/**/*.feature
5
+ LICENSE
data/.gitignore ADDED
@@ -0,0 +1,24 @@
1
+ ## MAC OS
2
+ .DS_Store
3
+
4
+ ## TEXTMATE
5
+ *.tmproj
6
+ tmtags
7
+
8
+ ## EMACS
9
+ *~
10
+ \#*
11
+ .\#*
12
+
13
+ ## RVM
14
+ .rvmrc
15
+
16
+ ## VIM
17
+ *.swp
18
+
19
+ ## PROJECT::GENERAL
20
+ coverage
21
+ rdoc
22
+ pkg
23
+
24
+ ## PROJECT::SPECIFIC
data/LICENSE ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2010 Harry Brundage
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.rdoc ADDED
@@ -0,0 +1,17 @@
1
+ = thwart
2
+
3
+ Simple, powerful, and developer friendly authorization plugin.
4
+
5
+ == Note on Patches/Pull Requests
6
+
7
+ * Fork the project.
8
+ * Make your feature addition or bug fix.
9
+ * Add tests for it. This is important so I don't break it in a
10
+ future version unintentionally.
11
+ * Commit, do not mess with rakefile, version, or history.
12
+ (if you want to have your own version, that is fine but bump version in a commit by itself I can ignore when I pull)
13
+ * Send me a pull request. Bonus points for topic branches.
14
+
15
+ == Copyright
16
+
17
+ Copyright (c) 2010 Harry Brundage. See LICENSE for details.
data/Rakefile ADDED
@@ -0,0 +1,30 @@
1
+ require 'rubygems'
2
+ require 'rake'
3
+
4
+ begin
5
+ require 'jeweler'
6
+ Jeweler::Tasks.new do |gem|
7
+ gem.name = "thwart"
8
+ gem.summary = %Q{A simple, powerful, and developer friendly authorization plugin. Still WIP.}
9
+ gem.description = %Q{Implements a robust Role Based Access System where Actors are granted permission to preform Actions by playing any number of Roles. All defined programatically in one place using a super easy DSL.}
10
+ gem.email = "harry@skylightlabs.ca"
11
+ gem.homepage = "http://github.com/hornairs/thwart"
12
+ gem.authors = ["Harry Brundage"]
13
+ gem.add_development_dependency "rspec", ">= 2.0.0.beta19"
14
+ gem.add_dependency "activesupport", ">= 3.0.rc1"
15
+ # gem is a Gem::Specification... see http://www.rubygems.org/read/chapter/20 for additional settings
16
+ end
17
+ Jeweler::GemcutterTasks.new
18
+ rescue LoadError
19
+ puts "Jeweler (or a dependency) not available. Install it with: gem install jeweler"
20
+ end
21
+
22
+ require 'rake/rdoctask'
23
+ Rake::RDocTask.new do |rdoc|
24
+ version = File.exist?('VERSION') ? File.read('VERSION') : ""
25
+
26
+ rdoc.rdoc_dir = 'rdoc'
27
+ rdoc.title = "thwart #{version}"
28
+ rdoc.rdoc_files.include('README*')
29
+ rdoc.rdoc_files.include('lib/**/*.rb')
30
+ end
data/VERSION ADDED
@@ -0,0 +1 @@
1
+ 0.0.0
@@ -0,0 +1 @@
1
+ Autotest.add_discovery { "rspec2" }
@@ -0,0 +1,56 @@
1
+ require File.expand_path(File.dirname(__FILE__) + '/example_helper')
2
+
3
+ Thwart.configure do
4
+ Thwart::Actions.add_crud!
5
+ default_query_response false
6
+
7
+ action_group :manage, [:view, :create, :update, :destroy]
8
+
9
+ role :employee do
10
+ view :all
11
+ update :this, :that
12
+ end
13
+
14
+ role :manager, :include => :employee do
15
+ allow do
16
+ destroy :this
17
+ end
18
+ deny do
19
+ destroy :that
20
+ end
21
+ end
22
+
23
+ role :administrator do
24
+ manage :all
25
+ end
26
+ end
27
+
28
+ @ed = User.new('Ed', :employee)
29
+ @mary = User.new('Mary', :manager)
30
+ @admin = User.new('Admin', :administrator)
31
+
32
+ @a_this = This.new
33
+ @a_that = That.new
34
+ @a_then = Then.new
35
+
36
+ puts @ed.can_view?(@a_this)
37
+ puts @ed.can_view?(@a_that)
38
+ puts @ed.can_view?(@a_then)
39
+ puts @ed.can_update?(@a_this)
40
+ puts @ed.can_update?(@a_that)
41
+ puts @ed.can_update?(@a_then)
42
+
43
+ # Thwart.query(:employee, :this, :view).should == true
44
+ # Thwart.query(:employee, @a_this, :view).should == true
45
+ # Thwart.query(@ed, :this, :view).should == true
46
+ # Thwart.query(@ed, @a_this, :view).should == true
47
+ #
48
+ # Thwart.query(:employee, :this, :update).should == true
49
+ # Thwart.query(:employee, @a_this, :update).should == true
50
+ # Thwart.query(@ed, :this, :update).should == true
51
+ # Thwart.query(@ed, @a_this, :update).should == true
52
+ #
53
+ # Thwart.query(:employee, :then, :update).should == true
54
+ # Thwart.query(:employee, @a_then, :update).should == true
55
+ # Thwart.query(@ed, :then, :update).should == true
56
+ # Thwart.query(@ed, @a_then, :update).should == true
@@ -0,0 +1,45 @@
1
+ require 'active_support'
2
+ $LOAD_PATH.unshift(File.dirname(__FILE__))
3
+ $LOAD_PATH.unshift(File.join(File.dirname(__FILE__), '..', 'lib'))
4
+ require 'thwart'
5
+
6
+ # require 'rubygems'
7
+ # require 'ruby-debug'
8
+
9
+ class User
10
+ include Thwart::Actor
11
+ thwart_access do
12
+ role_method :role
13
+ end
14
+ attr_accessor :name, :role
15
+ def initialize(n, r)
16
+ self.name = n
17
+ self.role = r
18
+ end
19
+ end
20
+
21
+ class Thing
22
+ include Thwart::Resource
23
+ thwart_access do
24
+ name :thing
25
+ end
26
+ end
27
+
28
+ class This < Thing
29
+ thwart_access do
30
+ name :this
31
+ end
32
+ end
33
+
34
+ class That < Thing
35
+ thwart_access do
36
+ name :that
37
+ end
38
+ end
39
+
40
+ class Then < Thing
41
+ thwart_access do
42
+ name :then
43
+ end
44
+ end
45
+
@@ -0,0 +1,62 @@
1
+ module Thwart
2
+ class ActionGroupBuilder
3
+ # Holds all the different action groups in actionables[:name] => [array of resolved actions]
4
+ attr_accessor :actionables
5
+ def actionables
6
+ @actionables ||= {}
7
+ @actionables
8
+ end
9
+
10
+ # Adds an actionable to the list of actionable things.
11
+ # @param name [symbol] The name of the action
12
+ # @param actions [symbol|array|nil] The actions this actionable resolves to. If nil, this is set to the name argument.
13
+ def add_actionable(name, actions = nil)
14
+ if actions.nil?
15
+ actions = Array.wrap(name)
16
+ else
17
+ actions = Array.wrap(actions)
18
+ end
19
+ self.actionables[name] = actions
20
+ end
21
+
22
+ # Creates an action group from an array of actionables.
23
+ # @param name [symbol] The name of the new action group
24
+ # @param others [symbol|array] One or an array symbols reffering to action or action groups
25
+ def create_action_group(name, others)
26
+ self.add_actionable(name, resolve_action_group(others))
27
+ end
28
+
29
+ # Resolves some actionables recursively down to the raw actions it corresponds to.
30
+ # @params name [symbol|array] the symbol (array) referring to the existing actionables
31
+ def resolve_action_group(name)
32
+ # - if name is an array => resolve it recursively
33
+ # - if name is in the action groups, pull out its existing resolution
34
+ # - otherwise, raise an error because we don't know what this is.
35
+ # Simple action groups (an action itself) must be added to the actions before they
36
+ # are referenced, which is accomplished using the :save callback on Actions
37
+
38
+ return name.map{|n| resolve_action_group(n)}.flatten.uniq if name.respond_to?(:map)
39
+ return self.actionables[name].flatten.uniq if self.actionables.include?(name)
40
+ puts self.actionables
41
+ raise Thwart::ActionOrGroupNotFoundError, "Action or group #{name} could not be found!"
42
+ end
43
+
44
+ # Adds the :create, :read, :update, and :destroy actionables
45
+ def add_crud_group!
46
+ @actions_store.add_crud! if @actions_store.respond_to?(:add_crud!)
47
+ if @crud.nil? || @crud == false
48
+ self.create_action_group(:crud, Thwart::CrudActions.keys)
49
+ @crud = true
50
+ end
51
+ end
52
+
53
+ def initialize(actions_store = Thwart::Actions)
54
+ builder = self
55
+ @actions_store = actions_store
56
+ @actions_store.class.set_callback :add, :after do |object|
57
+ builder.add_actionable(actions_store.last_action)
58
+ end
59
+ builder
60
+ end
61
+ end
62
+ end
@@ -0,0 +1,89 @@
1
+ module Thwart
2
+ class ActionsStore
3
+ include ActiveSupport::Callbacks
4
+ attr_accessor :last_action
5
+ attr_reader :actions
6
+ define_callbacks :add
7
+
8
+ def initialize
9
+ @actions = {}
10
+ end
11
+
12
+ # Returns true if Thwart is providing methods for the [action-able]_by? style action
13
+ # identifier, false otherwise.
14
+ #
15
+ # @param [Symbol] able_method The name of the [action-able]_by? method to check for.
16
+ def has_able?(able)
17
+ self.actions.has_value?(able)
18
+ end
19
+
20
+ # Returns true if Thwart is providing methods for the can_[action]? style action
21
+ # identifer, false otherwise.
22
+ #
23
+ # @param [Symbol] able_method The name of the can_[action]? method to check for.
24
+ def has_can?(can)
25
+ self.actions.has_key?(can)
26
+ end
27
+
28
+ # Finds the corresponding can from an able.
29
+ #
30
+ # @param [Symbol] able_method The name of the [action-able]_by? method.
31
+ def can_from_able(able)
32
+ self.actions.key(able)
33
+ end
34
+
35
+ # Adds an action to actions and the correct methods to can and able modules.
36
+ #
37
+ # @param [Symbol] can_method The name of the can_[action]? method.
38
+ # @param [Symbol] resource_method The name of the [action-able]_by? method.
39
+ def create_action(can, able = nil)
40
+ if able.nil?
41
+ if can.respond_to?(:each)
42
+ can.each {|c,a| self.create_action(c,a)}
43
+ else
44
+ raise ArgumentError, "able can't be nil"
45
+ end
46
+ else
47
+ run_callbacks :add do
48
+ @actions[can] = able
49
+ @last_action = can
50
+ end
51
+ end
52
+ end
53
+
54
+ # Finds the able name of the action from a [action-able]_by? style method.
55
+ #
56
+ # @param [Symbol] resource_method The name of the [action-able]_by? method.
57
+ # Adds an action to actions and the correct methods to can and able modules.
58
+ def find_able(name)
59
+ md = name.to_s.match(/(.+)_by\?/)
60
+ if md != nil && self.has_able?(md[1].intern)
61
+ md[1].intern
62
+ else
63
+ false
64
+ end
65
+ end
66
+
67
+ # Finds the can name of the action from a can_[action]? style method.
68
+ #
69
+ # @param [Symbol] can_method The name of the can_[action]? method.
70
+ def find_can(name)
71
+ md = name.to_s.match(/can_(.+)\?/)
72
+ if md != nil && self.has_can?(md[1].intern)
73
+ md[1].intern
74
+ else
75
+ false
76
+ end
77
+ end
78
+
79
+ # Add the CRUD methods to the Thwart actions (create, read, update, destroy)
80
+ def add_crud!
81
+ if @crud.nil? || @crud == false
82
+ Thwart::CrudActions.each do |(k,v)|
83
+ self.create_action(k, v)
84
+ end
85
+ @crud = true
86
+ end
87
+ end
88
+ end
89
+ end
@@ -0,0 +1,36 @@
1
+ module Thwart
2
+ module Actor
3
+ def self.included(base)
4
+ base.extend(ClassMethods)
5
+ end
6
+
7
+ module ClassMethods
8
+ attr_accessor :default_role, :role_from
9
+ # Thwart enabling hook on actors
10
+ def thwart_access(&block)
11
+ self.instance_eval do
12
+ include Thwart::Cans
13
+ include Thwart::Actor::InstanceMethods
14
+ end
15
+ # Set up DSL using dsl helper
16
+ if block_given?
17
+ dsl = Thwart::Dsl.new(:role_method => :role_from=, :role_proc => :role_from=, :role => :default_role=)
18
+ dsl.evaluate(self, &block)
19
+ end
20
+ end
21
+ end
22
+
23
+ module InstanceMethods
24
+ # The role of this particular actor instance
25
+ # If the class level @role_from is a symbol, it is the name of the instance method to get the role
26
+ # If the class level @role_from is a proc, call it
27
+ # If the above are unsuccessful, use the default if it exists
28
+ def thwart_role
29
+ r = self.send(self.class.role_from) if self.class.role_from.is_a?(Symbol) && self.respond_to?(self.class.role_from)
30
+ r ||= self.class.role_from.call(self) if self.class.role_from.respond_to?(:call)
31
+ r ||= self.class.default_role unless self.class.default_role.nil?
32
+ r
33
+ end
34
+ end
35
+ end
36
+ end
@@ -0,0 +1,30 @@
1
+ module Thwart
2
+ # Module in which the can_[action]? methods hang out
3
+ module Cans
4
+
5
+ def respond_to?(name)
6
+ return true if Thwart::Actions.find_can(name) != false
7
+ super
8
+ end
9
+
10
+ def method_missing(name, *args)
11
+ can = Thwart::Actions.find_can(name)
12
+ return Thwart.query(self, args.first, can) if args.length == 1 && !!can
13
+ super
14
+ end
15
+ end
16
+
17
+ # Module in which the [action]able_by? methods hang out
18
+ module Ables
19
+ def respond_to?(name)
20
+ return true if Thwart::Actions.find_able(name) != false
21
+ super
22
+ end
23
+
24
+ def method_missing(name, *args)
25
+ able = Thwart::Actions.find_able(name)
26
+ return Thwart.query(args.first, self, Thwart::Actions.can_from_able(able)) if args.length == 1 && !!able
27
+ super
28
+ end
29
+ end
30
+ end
data/lib/thwart/dsl.rb ADDED
@@ -0,0 +1,43 @@
1
+ module Thwart
2
+ class DslError < NoMethodError; end
3
+ class Dsl
4
+ attr_accessor :extra_methods, # Hash of the extra method mappings of this DSL => target
5
+ :method_map, # Holds the whole method map hash
6
+ :target, # What object the DSL maps methods on to
7
+ :all # Wheather or not to allow all methods (including dynamic ones like method missing) to be mapped
8
+
9
+ def initialize(map = {})
10
+ self.extra_methods = map
11
+ end
12
+
13
+ def evaluate(a_target, &block)
14
+ self.target = a_target
15
+ self.method_map = target.public_methods.inject({}) do |acc, m|
16
+ key = m.to_s.gsub("=", "").intern
17
+ acc[key] = m if acc[key].nil? || m != key
18
+ acc
19
+ end.merge(self.extra_methods)
20
+
21
+ self.instance_eval(&block)
22
+ self.target
23
+ end
24
+
25
+ def respond_to?(name)
26
+ if @all
27
+ return target.respond_to?(name)
28
+ else
29
+ return true if self.method_map.has_key?(name) && !!self.method_map[name]
30
+ super
31
+ end
32
+ end
33
+
34
+ def method_missing(name, *args, &block)
35
+ if self.respond_to?(name)
36
+ return self.target.send(self.method_map[name], *args, &block) if self.method_map.has_key?(name)
37
+ return self.target.send(name, *args, &block) if @all
38
+ end
39
+ super
40
+ end
41
+ end
42
+
43
+ end
@@ -0,0 +1,15 @@
1
+ module Thwart
2
+ module Enforcer
3
+ def thwart_access(resource)
4
+ raise ArgumentError, "Thwart needs a current_user method to enforce permissions." unless self.respond_to?(:current_user)
5
+ raise ArgumentError, "Thwart needs the params hash to have an [:action] to enforce." if params.nil? || params[:action].nil?
6
+ raise ArgumentError, "Unknown action #{params[:action]} to enforce" unless Thwart::Actions.has_can?(params[:action])
7
+
8
+ unless Thwart.query(current_user, resource, params[:action])
9
+ raise Thwart::NoPermissionError, "User #{current_user} doesn't have permission to #{params[:action]} #{resource}."
10
+ else
11
+ true
12
+ end
13
+ end
14
+ end
15
+ end
@@ -0,0 +1,27 @@
1
+ require 'active_support/inflector'
2
+ module Thwart::Resource
3
+ include Thwart::Ables
4
+ # Included hooks
5
+ def self.included(base)
6
+ base.extend ClassMethods
7
+ end
8
+
9
+ module ClassMethods
10
+ attr_accessor :thwart_name
11
+
12
+ def thwart_access(&block)
13
+ self.thwart_name = ActiveSupport::Inflector.singularize(self.table_name) if self.respond_to?(:table_name)
14
+
15
+ # Set up DSL using dsl helper
16
+ if block_given?
17
+ dsl = Thwart::Dsl.new(:name => :thwart_name=)
18
+ dsl.evaluate(self, &block)
19
+ end
20
+ self.ensure_attributes_set!
21
+ end
22
+
23
+ def ensure_attributes_set!
24
+ raise Thwart::MissingAttributeError if self.thwart_name == nil
25
+ end
26
+ end
27
+ end
@@ -0,0 +1,81 @@
1
+ module Thwart
2
+
3
+ module Role
4
+ attr_accessor :name, :default_response, :responses
5
+
6
+ def responses
7
+ @responses ||= {}
8
+ @responses
9
+ end
10
+
11
+ def parents
12
+ @parents ||= []
13
+ @parents
14
+ end
15
+
16
+ def parents=(p)
17
+ @parents = p.uniq
18
+ @parents
19
+ end
20
+
21
+ def query(actor, resource, action)
22
+ @query_result_found = false
23
+ resp = nil
24
+
25
+ if self.responses.has_key?(action)
26
+ # Find the resource scope response if it exists {:view => {:foo => bool}}
27
+ resp = self.resource_response(self.responses[action], resource) if !found?
28
+ # Find the action scope response if it exists {:view => bool}
29
+ resp = self.action_response(action) if !found?
30
+ end
31
+
32
+ # Return the default if it exists
33
+ resp = found!(self.default_response) if !found? && !self.default_response.nil?
34
+ # Call it if it is a proc
35
+ resp = resp.call(actor, resource, action) if resp.respond_to?(:call)
36
+
37
+ return resp
38
+ end
39
+
40
+ def resource_response(resources, resource)
41
+ # Return the resource scoped response if it exists
42
+ if resources.respond_to?(:[]) && resources.respond_to?(:include?)
43
+ if resources.include?(resource)
44
+ return found!(resources[resource])
45
+ elsif resources.include?(:_other)
46
+ return found!(resources[:_other])
47
+ end
48
+ end
49
+ nil
50
+ end
51
+
52
+ def action_response(action)
53
+ # Return the action level boolean, proc, or nil if it exists is the responses array
54
+ response = self.responses[action]
55
+ if self.responses.has_key?(action) && (response.is_a?(TrueClass) || response.is_a?(FalseClass) || response.nil? || response.respond_to?(:call))
56
+ return found!(response)
57
+ end
58
+ nil
59
+ end
60
+
61
+ private
62
+
63
+ def found!(response)
64
+ @query_result_found = true
65
+ response
66
+ end
67
+
68
+ def found?
69
+ @query_result_found ||= false
70
+ @query_result_found == true
71
+ end
72
+ end
73
+
74
+ class DefaultRole
75
+ include Thwart::Role
76
+ def query(*args)
77
+ return Thwart.default_query_response
78
+ end
79
+ end
80
+
81
+ end