thwart 0.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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