grant 1.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.rdoc +3 -4
- data/lib/grant/config_parser.rb +6 -10
- data/lib/grant/model_security.rb +5 -51
- data/lib/grant/version.rb +1 -1
- data/spec/config_parser_spec.rb +7 -34
- data/spec/model_security_spec.rb +1 -75
- metadata +5 -5
data/README.rdoc
CHANGED
@@ -2,7 +2,7 @@
|
|
2
2
|
|
3
3
|
Grant is a Ruby gem and Rails plugin that forces you to make explicit security decisions about the operations performed on your ActiveRecord models. It provides a declarative way to specify rules granting permission to perform CRUD operations on ActiveRecord objects.
|
4
4
|
|
5
|
-
Grant does not allow you to specify which operations are restricted. Instead, it restricts all CRUD operations unless they're explicitly granted to the user.
|
5
|
+
Grant does not allow you to specify which operations are restricted. Instead, it restricts all CRUD operations unless they're explicitly granted to the user. Only allowing operations explicitly granted forces you to make conscious security decisions. Grant will not help you make those decisions, but it won't let you forget to.
|
6
6
|
|
7
7
|
Additional information beyond that found in this README is available on the wiki[https://github.com/nearinfinity/grant/wiki].
|
8
8
|
|
@@ -40,7 +40,7 @@ Grant needs to know who the current user is, but with no standard for doing so y
|
|
40
40
|
|
41
41
|
= Usage
|
42
42
|
|
43
|
-
To enable model security you simply include the Grant::ModelSecurity module in your model class. In the example below you see
|
43
|
+
To enable model security you simply include the Grant::ModelSecurity module in your model class. In the example below you see two grant statements. The first grants find (aka read) permission all the time. The second example grants create, update, and destroy permission when the passed block evaluates to true, which in this case happens when the model is editable by the current user. A Grant::Error is raised if any grant block evaluates to false or nil.
|
44
44
|
|
45
45
|
class Book < ActiveRecord::Base
|
46
46
|
include Grant::ModelSecurity
|
@@ -48,14 +48,13 @@ To enable model security you simply include the Grant::ModelSecurity module in y
|
|
48
48
|
has_many :tags
|
49
49
|
grant(:find) { true }
|
50
50
|
grant(:create, :update, :destroy) { |user, model| model.editable_by_user? user }
|
51
|
-
grant(:add => :tags, :remove => :tags) { |user, model, associated_model| model.editable_by_user? user }
|
52
51
|
|
53
52
|
def editable_by_user? user
|
54
53
|
user.administrator? || user.has_role?(:editor)
|
55
54
|
end
|
56
55
|
end
|
57
56
|
|
58
|
-
The valid actions to pass to a grant statement are :find, :create, :update,
|
57
|
+
The valid actions to pass to a grant statement are :find, :create, :update, and :destroy. Each action can be passed as a Symbol or String. Any number of actions can be passed to a single grant statement, which is very useful if each of the actions share the same logic for determining access.
|
59
58
|
|
60
59
|
= Integration
|
61
60
|
|
data/lib/grant/config_parser.rb
CHANGED
@@ -2,24 +2,20 @@ module Grant
|
|
2
2
|
class ConfigParser
|
3
3
|
|
4
4
|
def self.extract_config(args)
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
[args, hash]
|
5
|
+
normalize_config args
|
6
|
+
validate_config args
|
7
|
+
args
|
10
8
|
end
|
11
9
|
|
12
10
|
private
|
13
11
|
|
14
|
-
def self.normalize_config(actions
|
12
|
+
def self.normalize_config(actions)
|
15
13
|
actions.each_with_index { |item, index| actions[index] = item.to_sym unless item.kind_of? Symbol }
|
16
|
-
associations.each_pair { |k, v| associations[k.to_sym] = associations.delete(k) unless k.kind_of? Symbol }
|
17
14
|
end
|
18
15
|
|
19
|
-
def self.validate_config(actions
|
20
|
-
raise Grant::Error.new("at least one :create, :find, :update, or :destroy action must be specified") if actions.empty?
|
16
|
+
def self.validate_config(actions)
|
17
|
+
raise Grant::Error.new("at least one :create, :find, :update, or :destroy action must be specified") if actions.empty?
|
21
18
|
raise Grant::Error.new(":create, :find, :update, and :destroy are the only valid actions") unless actions.all? { |a| [:create, :find, :update, :destroy].include? a }
|
22
|
-
raise Grant::Error.new(":add and :remove are the only valid association specifications") unless associations.keys.all? { |k| [:add, :remove].include? k }
|
23
19
|
end
|
24
20
|
|
25
21
|
end
|
data/lib/grant/model_security.rb
CHANGED
@@ -13,7 +13,7 @@ module Grant
|
|
13
13
|
grant_raise_error(grant_current_user, '#{action}', self) unless grant_disabled?
|
14
14
|
end
|
15
15
|
RUBY
|
16
|
-
base.send
|
16
|
+
base.send(callback.to_sym, "grant_#{callback}".to_sym)
|
17
17
|
end
|
18
18
|
|
19
19
|
base.extend ClassMethods
|
@@ -30,28 +30,17 @@ module Grant
|
|
30
30
|
Grant::ThreadStatus.disabled? || @grant_disabled
|
31
31
|
end
|
32
32
|
|
33
|
-
def grant_raise_error(user, action, model, association_id=nil
|
33
|
+
def grant_raise_error(user, action, model, association_id=nil)
|
34
34
|
msg = ["#{action} permission",
|
35
35
|
"not granted to #{user.class.name}:#{user.id}",
|
36
36
|
"for resource #{model.class.name}:#{model.id}"]
|
37
|
-
msg.insert(1, "to #{association_id}:#{associated_model.class.name} association") if association_id && associated_model
|
38
37
|
|
39
38
|
raise Grant::Error.new(msg.join(' '))
|
40
39
|
end
|
41
40
|
|
42
41
|
module ClassMethods
|
43
42
|
def grant(*args, &blk)
|
44
|
-
actions
|
45
|
-
|
46
|
-
associations.each_pair do |action, association_ids|
|
47
|
-
Array(association_ids).each do |association_id|
|
48
|
-
grant_callback = "grant_#{action}_#{association_id}".to_sym
|
49
|
-
define_method(grant_callback) do |associated_model|
|
50
|
-
grant_raise_error(grant_current_user, action, self, association_id, associated_model) unless grant_disabled? || blk.call(grant_current_user, self, associated_model)
|
51
|
-
end
|
52
|
-
end
|
53
|
-
end
|
54
|
-
|
43
|
+
actions = Grant::ConfigParser.extract_config(args)
|
55
44
|
actions.each do |action|
|
56
45
|
grant_callback = (action.to_sym == :find ? "grant_after_find" : "grant_before_#{action}").to_sym
|
57
46
|
define_method(grant_callback) do
|
@@ -59,43 +48,8 @@ module Grant
|
|
59
48
|
end
|
60
49
|
end
|
61
50
|
end
|
62
|
-
|
63
|
-
def has_and_belongs_to_many(association_id, options={}, &extension)
|
64
|
-
add_grant_association_callback(:add, association_id, options)
|
65
|
-
add_grant_association_callback(:remove, association_id, options)
|
66
|
-
super
|
67
|
-
end
|
68
|
-
|
69
|
-
def has_many(association_id, options={}, &extension)
|
70
|
-
add_grant_association_callback(:add, association_id, options)
|
71
|
-
add_grant_association_callback(:remove, association_id, options)
|
72
|
-
super
|
73
|
-
end
|
74
|
-
|
75
|
-
private
|
76
|
-
|
77
|
-
def add_grant_association_callback(action, association_id, options)
|
78
|
-
callback_name = "before_#{action}".to_sym
|
79
|
-
callback = "grant_#{action}_#{association_id}".to_sym
|
80
|
-
unless self.instance_methods.include? callback.to_s
|
81
|
-
class_eval <<-RUBY
|
82
|
-
def #{callback}(associated_model)
|
83
|
-
grant_raise_error(grant_current_user, '#{action}', self, '#{association_id}', associated_model) unless grant_disabled?
|
84
|
-
end
|
85
|
-
RUBY
|
86
|
-
end
|
87
|
-
|
88
|
-
if options.has_key? callback_name
|
89
|
-
if options[callback_name].kind_of? Array
|
90
|
-
options[callback_name].insert(0, callback)
|
91
|
-
else
|
92
|
-
options[callback_name] = [callback, options[callback_name]]
|
93
|
-
end
|
94
|
-
else
|
95
|
-
options[callback_name] = callback
|
96
|
-
end
|
97
|
-
end
|
98
|
-
|
99
51
|
end
|
52
|
+
|
100
53
|
end
|
54
|
+
|
101
55
|
end
|
data/lib/grant/version.rb
CHANGED
data/spec/config_parser_spec.rb
CHANGED
@@ -4,56 +4,29 @@ require 'grant'
|
|
4
4
|
describe Grant::ConfigParser do
|
5
5
|
|
6
6
|
describe 'Configuration' do
|
7
|
-
it "should parse actions
|
8
|
-
config = Grant::ConfigParser.extract_config([:create, 'update'
|
7
|
+
it "should parse actions from a config array" do
|
8
|
+
config = Grant::ConfigParser.extract_config([:create, 'update'])
|
9
9
|
config.should_not be_nil
|
10
|
-
config.should
|
11
|
-
config[0].should =~ [:create, :update]
|
12
|
-
config[1].should == {:add => [:people, :places], :remove => :people}
|
10
|
+
config.should =~ [:create, :update]
|
13
11
|
end
|
14
12
|
|
15
|
-
it "should parse actions from a config array when associations are absent" do
|
16
|
-
config = Grant::ConfigParser.extract_config([:create, :update])
|
17
|
-
config.should_not be_nil
|
18
|
-
config.should have(2).items
|
19
|
-
config[0].should =~ [:create, :update]
|
20
|
-
config[1].should == {}
|
21
|
-
end
|
22
|
-
|
23
|
-
it "should parse actions and associations from a config array when options are absent" do
|
24
|
-
config = Grant::ConfigParser.extract_config([:create, 'update', {:add => ['people', :places]}])
|
25
|
-
config.should_not be_nil
|
26
|
-
config.should have(2).items
|
27
|
-
config[0].should =~ [:create, :update]
|
28
|
-
config[1].should == {:add => ['people', :places]}
|
29
|
-
end
|
30
|
-
|
31
13
|
it "should parse actions" do
|
32
14
|
config = Grant::ConfigParser.extract_config([:create])
|
33
15
|
config.should_not be_nil
|
34
|
-
config.should
|
35
|
-
config[0].should =~ [:create]
|
36
|
-
config[1].should == {}
|
16
|
+
config.should =~ [:create]
|
37
17
|
end
|
38
|
-
|
39
18
|
end
|
40
19
|
|
41
20
|
describe 'Configuration Validation' do
|
42
|
-
it "should raise a Grant::Error if no action
|
21
|
+
it "should raise a Grant::Error if no action is specified" do
|
43
22
|
lambda {
|
44
|
-
Grant::ConfigParser.instance_eval { validate_config([]
|
23
|
+
Grant::ConfigParser.instance_eval { validate_config([]) }
|
45
24
|
}.should raise_error(Grant::Error)
|
46
25
|
end
|
47
26
|
|
48
27
|
it "should raise a Grant::Error if an invalid action is specified" do
|
49
28
|
lambda {
|
50
|
-
Grant::ConfigParser.instance_eval { validate_config([:create, :udate]
|
51
|
-
}.should raise_error(Grant::Error)
|
52
|
-
end
|
53
|
-
|
54
|
-
it "should raise a Grant::Error if an invalid association is specified" do
|
55
|
-
lambda {
|
56
|
-
Grant::ConfigParser.instance_eval { validate_config([:destroy], {:add => :people, :update => :places}) }
|
29
|
+
Grant::ConfigParser.instance_eval { validate_config([:create, :udate]) }
|
57
30
|
}.should raise_error(Grant::Error)
|
58
31
|
end
|
59
32
|
end
|
data/spec/model_security_spec.rb
CHANGED
@@ -13,15 +13,6 @@ describe Grant::ModelSecurity do
|
|
13
13
|
it 'should establish failing ActiveRecord callbacks for before_create, before_update, before_destroy, and after_find when included' do
|
14
14
|
verify_standard_callbacks(new_model_class.new)
|
15
15
|
end
|
16
|
-
|
17
|
-
it 'should establish failing ActiveRecord callbacks for any has_many or has_and_belongs_to_many associations' do
|
18
|
-
c = new_model_class
|
19
|
-
c.instance_eval do
|
20
|
-
has_many :users
|
21
|
-
has_and_belongs_to_many :groups
|
22
|
-
end
|
23
|
-
verify_association_callbacks(c.new)
|
24
|
-
end
|
25
16
|
end
|
26
17
|
|
27
18
|
describe '#grant' do
|
@@ -55,59 +46,12 @@ describe Grant::ModelSecurity do
|
|
55
46
|
c = new_model_class.instance_eval { grant(:create, :update, :destroy, :find) { true }; self }
|
56
47
|
verify_standard_callbacks(c.new, :create, :update, :destroy, :find)
|
57
48
|
end
|
58
|
-
|
59
|
-
it 'should allow adding to an association to succeed when granted' do
|
60
|
-
c = new_model_class
|
61
|
-
c.instance_eval do
|
62
|
-
has_many :users
|
63
|
-
has_and_belongs_to_many :groups
|
64
|
-
grant(:add => [:users, :groups]) { true }
|
65
|
-
end
|
66
|
-
verify_association_callbacks(c.new, :add_users, :add_groups)
|
67
|
-
end
|
68
|
-
|
69
|
-
it 'should allow adding to an association to succeed when granted above association declarations' do
|
70
|
-
c = new_model_class
|
71
|
-
c.instance_eval do
|
72
|
-
grant(:add => [:users, :groups]) { true }
|
73
|
-
has_many :users
|
74
|
-
has_and_belongs_to_many :groups, :before_add => [:bogus1, :bogus2]
|
75
|
-
define_method(:bogus1) {}
|
76
|
-
define_method(:bogus2) {}
|
77
|
-
end
|
78
|
-
verify_association_callbacks(c.new, :add_users, :add_groups)
|
79
|
-
end
|
80
|
-
|
81
|
-
it 'should allow removing from an association to succeed when granted' do
|
82
|
-
c = new_model_class
|
83
|
-
c.instance_eval do
|
84
|
-
has_many :users
|
85
|
-
has_and_belongs_to_many :groups, :before_remove => :bogus1
|
86
|
-
grant(:remove => [:users, :groups]) { true }
|
87
|
-
define_method(:bogus1) {}
|
88
|
-
end
|
89
|
-
verify_association_callbacks(c.new, :remove_users, :remove_groups)
|
90
|
-
end
|
91
|
-
|
92
|
-
it 'should allow removing from an association to succeed when granted above association declarations' do
|
93
|
-
c = new_model_class
|
94
|
-
c.instance_eval do
|
95
|
-
grant(:remove => [:users, :groups]) { true }
|
96
|
-
has_many :users
|
97
|
-
has_and_belongs_to_many :groups
|
98
|
-
end
|
99
|
-
verify_association_callbacks(c.new, :remove_users, :remove_groups)
|
100
|
-
end
|
101
49
|
end
|
102
|
-
|
50
|
+
|
103
51
|
def verify_standard_callbacks(instance, *succeeding_callbacks)
|
104
52
|
verify_callbacks([:create, :update, :destroy, :find], instance, nil, succeeding_callbacks)
|
105
53
|
end
|
106
54
|
|
107
|
-
def verify_association_callbacks(instance, *succeeding_callbacks)
|
108
|
-
verify_callbacks([:add_users, :remove_users, :add_groups, :remove_groups], instance, new_model_class.new, succeeding_callbacks)
|
109
|
-
end
|
110
|
-
|
111
55
|
def verify_callbacks(all_actions, instance, associated_model, succeeding_callbacks)
|
112
56
|
all_actions.each do |action|
|
113
57
|
expectation = succeeding_callbacks.include?(action) ? :should_not : :should
|
@@ -139,24 +83,6 @@ describe Grant::ModelSecurity do
|
|
139
83
|
def self.after_find(method)
|
140
84
|
define_method(:find) { send method }
|
141
85
|
end
|
142
|
-
|
143
|
-
def self.has_many(association_id, options, &extension)
|
144
|
-
define_method("add_#{association_id}".to_sym) do |associated_model|
|
145
|
-
Array(options[:before_add]).each { |callback| send(callback, associated_model) }
|
146
|
-
end
|
147
|
-
define_method("remove_#{association_id}".to_sym) do |associated_model|
|
148
|
-
Array(options[:before_remove]).each { |callback| send(callback, associated_model) }
|
149
|
-
end
|
150
|
-
end
|
151
|
-
|
152
|
-
def self.has_and_belongs_to_many(association_id, options, &extension)
|
153
|
-
define_method("add_#{association_id}".to_sym) do |associated_model|
|
154
|
-
Array(options[:before_add]).each { |callback| send(callback, associated_model) }
|
155
|
-
end
|
156
|
-
define_method("remove_#{association_id}".to_sym) do |associated_model|
|
157
|
-
Array(options[:before_remove]).each { |callback| send(callback, associated_model) }
|
158
|
-
end
|
159
|
-
end
|
160
86
|
end
|
161
87
|
|
162
88
|
end
|
metadata
CHANGED
@@ -1,13 +1,13 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: grant
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
hash:
|
4
|
+
hash: 15
|
5
5
|
prerelease: false
|
6
6
|
segments:
|
7
|
-
-
|
7
|
+
- 2
|
8
8
|
- 0
|
9
|
-
-
|
10
|
-
version:
|
9
|
+
- 0
|
10
|
+
version: 2.0.0
|
11
11
|
platform: ruby
|
12
12
|
authors:
|
13
13
|
- Jeff Kunkle
|
@@ -16,7 +16,7 @@ autorequire:
|
|
16
16
|
bindir: bin
|
17
17
|
cert_chain: []
|
18
18
|
|
19
|
-
date:
|
19
|
+
date: 2011-01-05 00:00:00 -05:00
|
20
20
|
default_executable:
|
21
21
|
dependencies:
|
22
22
|
- !ruby/object:Gem::Dependency
|