permit 0.9.0 → 1.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,4 +1,5 @@
1
1
  module Permit
2
+ # Collection of PermitRule objects defining authorization.
2
3
  class PermitRules
3
4
  include Permit::Support
4
5
 
@@ -44,20 +45,32 @@ module Permit
44
45
  #
45
46
  # @example Allow a person that is a member of a team to show
46
47
  # allow :person, :who => :is_member, :of => :team, :to => :show
48
+ # @example Allow a person that is a member of any of the teams to index.
49
+ # allow :person, :who => :is_member, :of => [:team1, :team2], :to => :index
47
50
  # @example Allow a person with either of the named roles for a resource to perform any "write" operations.
48
51
  # allow [:project_admin, :project_manager], :of => :project, :to => :write
52
+ # @example Allow a person with the viewer role of either of the projects to show.
53
+ # allow :viewer, :of => [:project1, :project2], :to => :show
49
54
  #
50
55
  # @param [Symbol, <Symbol>] roles the role(s) that the rule will apply to.
51
56
  # @param [Hash] options the options used to build the rule.
52
57
  # @option options [Symbol] :who the method to call on the target resource.
53
58
  # @option options [Symbol] :that alias for :who
54
- # @option options [Symbol] :of the name of the instance variable holding the target
59
+ # @option options [Symbol, nil, :any, <Symbol, nil>] :of the name of the instance variable holding the target
55
60
  # resource. If set to +:any+ then the match will apply to a person that has
56
61
  # a matching role authorization for any resource. If not given, or set to
57
62
  # +nil+, then the match will apply to a person that has a matching role
58
63
  # authorization for a nil resource. +:any/nil+ functionality only applies
59
64
  # when using named roles. (see Permit::NamedRoles).
60
- # @option options [Symbol] :on alias for +:of+
65
+ # @option options [Symbol, nil, :any, <Symbol, nil>] :on alias for +:of+
66
+ # @option options [Symbol, String, Proc] :if code to evaluate at the end of the
67
+ # match if it is still valid. If it returns false, the rule will not
68
+ # match. If a proc if given, it will be passed the current subject and
69
+ # binding. A method will be called without any arguments.
70
+ # @option options [Symbol, String, Proc] :unless code to evaluate at the end
71
+ # of the match if it is still valid. If it returns true, the rule will not
72
+ # match. If a proc if given, it will be passed the current subject and
73
+ # binding. A method will be called without any arguments.
61
74
  # @option options [Symbol, <Symbol>] :to the action(s) to allow access to if this
62
75
  # rule matches. +:all+ may be given to indicate that access is given to all
63
76
  # actions if the rule matches. Actions will be expanded using the aliases
@@ -91,6 +104,14 @@ module Permit
91
104
  # authorization for a nil resource. :any/nil functionality only applies
92
105
  # when using named roles. (see Permit::NamedRoles).
93
106
  # @option options [Symbol] :on alias for +:of+
107
+ # @option options [Symbol, String, Proc] :if code to evaluate at the end of the
108
+ # match if it is still valid. If it returns false, the rule will not
109
+ # match. The proc or method called, will be passed the current subject
110
+ # being matched, and the binding being used.
111
+ # @option options [Symbol, String, Proc] :unless code to evaluate at the end
112
+ # of the match if it is still valid. If it returns true, the rule will not
113
+ # match. The proc or method called, will be passed the current subject
114
+ # being matched, and the binding being used.
94
115
  # @option options [Symbol, <Symbol>] :from the action(s) to deny access to if this
95
116
  # rule matches. +:all+ may be given to indicate that access is denied to all
96
117
  # actions if the rule matches. Actions will be expanded using the aliases
@@ -32,28 +32,49 @@ module Permit
32
32
  end
33
33
 
34
34
  protected
35
- def authorization_conditions(role, resource, person = nil)
36
- conditions = {}
37
- conditions[Permit::Config.person_class.name.foreign_key] = person.id if person
38
- conditions.merge! role_condition(role)
39
- conditions.merge! resource_conditions(resource)
35
+ def authorization_conditions(roles, resources, person = nil)
36
+ params = {}
37
+ query = []
38
+ if person
39
+ query << "#{Permit::Config.person_class.name.foreign_key} = :person_id"
40
+ params[:person_id] = person.id
41
+ end
42
+ query << role_condition(roles, params)
43
+ query << resource_conditions(resources, params)
44
+
45
+ [query.compact.join(" AND "), params]
40
46
  end
41
47
 
42
- def role_condition(roles)
43
- return {} unless roles
48
+ def role_condition(roles, params = {})
49
+ return nil unless roles
44
50
 
45
51
  r = get_roles(roles)
46
52
  ids = r.collect {|role| role.id}
47
53
 
48
- return (ids.empty? ? {} : {Permit::Config.role_class.name.foreign_key => ids})
54
+ if ids.empty?
55
+ return nil
56
+ else
57
+ params[:role_ids] = ids
58
+ "#{Permit::Config.role_class.name.foreign_key} in (:role_ids)"
59
+ end
49
60
  end
50
61
 
51
- def resource_conditions(resource)
52
- case resource
53
- when :any then {}
54
- when nil then {:resource_type => nil, :resource_id => nil}
55
- else {:resource_type => resource.class.resource_type, :resource_id => resource.id}
62
+ def resource_conditions(resources, params = {})
63
+ constraints = []
64
+ permit_arrayify(resources).each_with_index do |resource, idx|
65
+ type, id = case resource
66
+ when :any then return nil
67
+ when nil then [nil, nil]
68
+ else [resource.class.resource_type, resource.id]
69
+ end
70
+
71
+ type_key = "resource_type_#{idx}".to_sym
72
+ id_key = "resource_id_#{idx}".to_sym
73
+ params.merge! type_key => type, id_key => id
74
+ op = type.nil? ? 'is' : '='
75
+ constraints << "(resource_type #{op} #{type_key.inspect} AND resource_id #{op} #{id_key.inspect})"
56
76
  end
77
+ return "(" << constraints.join(" OR ") << ")"
57
78
  end
58
79
 
59
80
  def get_role(role)
data/permit.gemspec CHANGED
@@ -5,11 +5,11 @@
5
5
 
6
6
  Gem::Specification.new do |s|
7
7
  s.name = %q{permit}
8
- s.version = "0.9.0"
8
+ s.version = "1.0.0"
9
9
 
10
10
  s.required_rubygems_version = Gem::Requirement.new(">= 0") if s.respond_to? :required_rubygems_version=
11
11
  s.authors = ["Steve Valaitis"]
12
- s.date = %q{2010-03-27}
12
+ s.date = %q{2010-04-14}
13
13
  s.email = %q{steve@digitalnothing.com}
14
14
  s.extra_rdoc_files = [
15
15
  "README.mkd"
@@ -60,7 +60,7 @@ Gem::Specification.new do |s|
60
60
  s.homepage = %q{http://github.com/dnd/permit}
61
61
  s.rdoc_options = ["--charset=UTF-8"]
62
62
  s.require_paths = ["lib"]
63
- s.rubygems_version = %q{1.3.5}
63
+ s.rubygems_version = %q{1.3.6}
64
64
  s.summary = %q{A flexible authorization plugin for Ruby on Rails.}
65
65
  s.test_files = [
66
66
  "spec/spec_helper.rb",
@@ -14,6 +14,7 @@ module Permit::Specs
14
14
  @tom = Person.create :name => "tom"
15
15
  @hotness = Project.create(:name => "hotness")
16
16
  @maintenance = Project.create(:name => "maintenance")
17
+ @project_x = Project.create!(:name => "project x")
17
18
  Role.create :key => :site_admin, :name => 'site admin', :authorize_resource => false, :requires_resource => false
18
19
  new_authz @bob, :site_admin, nil
19
20
  new_authz @bob, :admin, @maintenance
@@ -23,45 +24,119 @@ module Permit::Specs
23
24
  new_authz @tom, :admin, @hotness
24
25
  end
25
26
 
26
- context "#authorized?" do
27
- it "should return true if the person has any of the roles for the resource" do
28
- @bob.should be_authorized(:admin, @maintenance)
29
- end
27
+ describe "#authorized?" do
28
+ context "for one resource" do
29
+ it "should return true if the person has any of the roles for the resource" do
30
+ @bob.should be_authorized([:admin, :team_lead], @maintenance)
31
+ end
30
32
 
31
- it "should return true if the person has any of the resources for the passed in Role object" do
32
- r = Role.find_by_key 'admin'
33
- @bob.should be_authorized(r, @maintenance)
34
- end
33
+ it "should return true if the person has any of the resources for the passed in Role object" do
34
+ r = Role.find_by_key 'admin'
35
+ @bob.should be_authorized(r, @maintenance)
36
+ end
37
+
38
+ it "should return false if the person does not have any of the roles for the resource" do
39
+ @bob.should_not be_authorized(:admin, @hotness)
40
+ end
41
+
42
+ it "should return false for a role that doesn't exist" do
43
+ @bob.should_not be_authorized(:lead_monkey_tech, @maintenance)
44
+ end
35
45
 
36
- it "should return false if the person does not have any of the roles for the resource" do
37
- @bob.should_not be_authorized(:admin, @hotness)
46
+ it "should return true if the person has a nil resource authorization for a role" do
47
+ @bob.should be_authorized(:site_admin, nil)
48
+ end
38
49
  end
39
50
 
40
- it "should return false for a role that doesn't exist" do
41
- @bob.should_not be_authorized(:lead_monkey_tech, @maintenance)
51
+ context "for multiple resources" do
52
+ it "should return true if the person has any of the roles for the resources" do
53
+ @bob.should be_authorized([:admin, :team_lead], [@maintenance, @project_x])
54
+ end
55
+
56
+ it "should return true if the person has any of the resources for the passed in Role object" do
57
+ r = Role.find_by_key 'admin'
58
+ @bob.should be_authorized(r, [@maintenance, @hotness])
59
+ end
60
+
61
+ it "should return false if the person does not have any of the roles for the resources" do
62
+ @bob.should_not be_authorized(:admin, [@hotness, @project_x])
63
+ end
64
+
65
+ it "should return false for a role that doesn't exist" do
66
+ @bob.should_not be_authorized(:lead_monkey_tech, [@maintenance, @hotness])
67
+ end
68
+
69
+ it "should return true if the person has a nil resource authorization for a role" do
70
+ @bob.should be_authorized(:site_admin, [nil, @project_x])
71
+ end
42
72
  end
43
73
 
44
- it "should return true if the person has a nil resource authorization for a role" do
45
- @bob.should be_authorized(:site_admin, nil)
74
+ context "for any resource" do
75
+ it "should return true if the person has any of the roles for any resource" do
76
+ @bob.should be_authorized([:admin, :team_lead], :any)
77
+ end
78
+
79
+ it "should return false if the person does not have any of the roles for any resources" do
80
+ @tom.should_not be_authorized([:team_lead, :monkey_tech], :any)
81
+ end
46
82
  end
47
83
  end
48
84
 
49
- context "#authorized_all?" do
50
- it "should return true if the person matches all of the roles for the resource" do
51
- @bob.should be_authorized_all([:admin, :developer], @maintenance)
52
- end
85
+ describe "#authorized_all?" do
86
+ context "for one resource" do
87
+ it "should return true if the person matches all of the roles for the resource" do
88
+ @bob.should be_authorized_all([:admin, :developer], @maintenance)
89
+ end
53
90
 
54
- it "should return true if the person matches all of the roles for the resource when a Role object is passed" do
55
- r = Role.find_by_key 'developer'
56
- @bob.should be_authorized_all([r, :admin], @maintenance)
91
+ it "should return true if the person matches all of the roles for the resource when a Role object is passed" do
92
+ r = Role.find_by_key 'developer'
93
+ @bob.should be_authorized_all([r, :admin], @maintenance)
94
+ end
95
+
96
+ it "should return false if the person does not match all of the roles for the resource" do
97
+ @tom.should_not be_authorized_all([:admin, :team_lead], @hotness)
98
+ end
99
+
100
+ it "should return false if one of the roles does not exist" do
101
+ @tom.should_not be_authorized_all([:admin, :slinky_analyst], @hotness)
102
+ end
57
103
  end
58
104
 
59
- it "should return false if the person does not match all of the roles for the resource" do
60
- @tom.should_not be_authorized_all([:admin, :team_lead], @hotness)
105
+ context "for multiple resources" do
106
+ it "should return true if the person matches all of the roles for the resource" do
107
+ @bob.authorize([:admin, :developer], @project_x)
108
+ @bob.should be_authorized_all([:admin, :developer], [@maintenance, @project_x])
109
+ end
110
+
111
+ it "should return true if the person matches all of the roles for the resource when a Role object is passed" do
112
+ @bob.authorize([:admin, :developer], @project_x)
113
+ r = Role.find_by_key 'developer'
114
+ @bob.should be_authorized_all([r, :admin], [@maintenance, @project_x])
115
+ end
116
+
117
+ it "should return false if the person does not match all of the roles for the resource" do
118
+ @tom.should_not be_authorized_all([:admin, :team_lead], @hotness)
119
+ end
120
+
121
+ it "should return false if the person does not match all of the roles for the resources" do
122
+ @bob.authorize :admin, @project_x
123
+ @bob.should_not be_authorized_all([:admin, :developer], [@maintenance, @project_x])
124
+ end
125
+
126
+ it "should return false if one of the roles does not exist" do
127
+ @tom.should_not be_authorized_all([:admin, :slinky_analyst], [@hotness, @maintenance])
128
+ end
61
129
  end
62
130
 
63
- it "should return false if one of the roles does not exist" do
64
- @tom.should_not be_authorized_all([:admin, :slinky_analyst], @hotness)
131
+ context "for any resource" do
132
+ it "should return true if the person has all of the roles for any resource" do
133
+ @bob.authorize :admin, @project_x
134
+ @bob.should be_authorized_all([:admin, :team_lead], :any)
135
+ end
136
+
137
+ it "should return false if the person does not have all of the roles for any resource" do
138
+ @tom.should_not be_authorized_all([:admin, :team_lead], :any)
139
+ end
65
140
  end
66
141
  end
67
142
  end
@@ -223,20 +298,23 @@ module Permit::Specs
223
298
  new_authz @tom, :admin, @hotness
224
299
  end
225
300
 
226
- it "#authorizations.roles_for should return the roles the current person has for the resource" do
227
- roles = @bob.authorizations.roles_for @maintenance
228
- roles.should have(2).items
301
+ it "#authorizations.roles_for should return the roles the current person has for the resources" do
302
+ roles = @bob.authorizations.roles_for [@maintenance, @hotness]
303
+ roles.should have(3).items
229
304
  roles[0].key.should == 'admin'
230
305
  roles[1].key.should == 'developer'
306
+ roles[2].key.should == 'team_lead'
231
307
  end
232
308
 
233
- it "#authorizations.for should return the authorizations the current person has for the resource" do
234
- authz = @bob.authorizations.for @maintenance
235
- authz.should have(2).items
309
+ it "#authorizations.for should return the authorizations the current person has for the resources" do
310
+ authz = @bob.authorizations.for [@maintenance, @hotness]
311
+ authz.should have(3).items
236
312
  authz[0].role.key.should == 'admin'
237
313
  authz[0].resource.should == @maintenance
238
314
  authz[1].role.key.should == 'developer'
239
315
  authz[1].resource.should == @maintenance
316
+ authz[2].role.key.should == 'team_lead'
317
+ authz[2].resource.should == @hotness
240
318
  end
241
319
  end
242
320
 
@@ -101,6 +101,13 @@ module Permit::Specs
101
101
  people[1].should == @jack
102
102
  end
103
103
 
104
+ it "#authorizations.people_for should return the people authorized for the resources on the current role" do
105
+ people = @admin.authorizations.people_for [@maintenance, @hotness]
106
+ people.should have(2).items
107
+ people[0].should == @bob
108
+ people[1].should == @tom
109
+ end
110
+
104
111
  it "#authorizations.for should return the authorizations the current role has for the resource" do
105
112
  authz = @admin.authorizations.for @maintenance
106
113
  authz.should have(1).items
@@ -37,12 +37,12 @@ module Permit::Specs
37
37
  authorized?(roles, resource)
38
38
  end
39
39
 
40
- def pub_allowed?(roles, options = {})
41
- allowed?(roles, options)
40
+ def pub_allowed?(*args)
41
+ allowed?(*args)
42
42
  end
43
43
 
44
- def pub_denied?(roles, options = {})
45
- denied?(roles, options)
44
+ def pub_denied?(*args)
45
+ denied?(*args)
46
46
  end
47
47
  end
48
48
 
@@ -212,62 +212,102 @@ module Permit::Specs
212
212
  end
213
213
 
214
214
  describe "#allowed?" do
215
- it "should properly build the rule" do
216
- controller.stub!(:current_person).and_return(Guest.new)
217
- rule = PermitRule.new :admin, :on => :team
218
- PermitRule.should_receive(:new).with(:admin, hash_including(:on => :team)).and_return(rule)
219
- controller.pub_allowed?(:admin, :on => :team)
215
+ context "for a rule" do
216
+ it "should properly build the rule" do
217
+ controller.stub!(:current_person).and_return(Guest.new)
218
+ rule = PermitRule.new :admin, :on => :team
219
+ PermitRule.should_receive(:new).with(:admin, hash_including(:on => :team)).and_return(rule)
220
+ controller.pub_allowed?(:admin, :on => :team)
221
+ end
222
+
223
+ it "should properly call #matches? on the rule" do
224
+ bob = Person.create! :name => 'bob'
225
+ controller.stub!(:current_person).and_return(bob)
226
+ rule = PermitRule.new :guest
227
+ rule.should_receive(:matches?).with(bob, instance_of(Binding))
228
+ PermitRule.stub!(:new).and_return(rule)
229
+ controller.pub_allowed?(:guest)
230
+ end
231
+
232
+ it "should return the result of the rule match" do
233
+ controller.stub!(:current_person).and_return(Guest.new)
234
+ rule = PermitRule.new :guest
235
+ PermitRule.stub!(:new).and_return(rule)
236
+
237
+ rule.stub!(:matches?).and_return(true)
238
+ controller.pub_allowed?(:guest).should be_true
239
+
240
+ rule.stub!(:matches?).and_return(false)
241
+ controller.pub_allowed?(:guest).should be_false
242
+ end
220
243
  end
221
244
 
222
- it "should properly call #matches? on the rule" do
223
- bob = Person.create! :name => 'bob'
224
- controller.stub!(:current_person).and_return(bob)
225
- rule = PermitRule.new :guest
226
- rule.should_receive(:matches?).with(bob, instance_of(Binding))
227
- PermitRule.stub!(:new).and_return(rule)
228
- controller.pub_allowed?(:guest)
245
+ context "for an action on the current controller" do
246
+ it "should evaluate the rules for the action on the current controller" do
247
+ guest = Guest.new
248
+ controller.stub!(:current_person).and_return(guest)
249
+ ProjectsController.permit_rules.should_receive(:permitted?).with(guest, :show, instance_of(Binding)).and_return(true)
250
+ controller.pub_allowed?(:action => :show).should be_true
251
+ end
229
252
  end
230
253
 
231
- it "should return the result of the rule match" do
232
- controller.stub!(:current_person).and_return(Guest.new)
233
- rule = PermitRule.new :guest
234
- PermitRule.stub!(:new).and_return(rule)
235
-
236
- rule.stub!(:matches?).and_return(true)
237
- controller.pub_allowed?(:guest).should be_true
238
-
239
- rule.stub!(:matches?).and_return(false)
240
- controller.pub_allowed?(:guest).should be_false
254
+ context "for an action on a different controller" do
255
+ it "should evaluate the rules for the action on the given controller" do
256
+ guest = Guest.new
257
+ controller.stub!(:current_person).and_return(guest)
258
+ TeamsController.permit_rules.should_receive(:permitted?).with(guest, :index, instance_of(Binding)).and_return(true)
259
+ controller.pub_allowed?(:controller => 'permit/specs/teams', :action => :index).should be_true
260
+ end
241
261
  end
242
262
  end
243
263
 
244
264
  describe "#denied?" do
245
- it "should properly build the rule" do
246
- controller.stub!(:current_person).and_return(Guest.new)
247
- rule = PermitRule.new :admin, :on => :team
248
- PermitRule.should_receive(:new).with(:admin, hash_including(:on => :team)).and_return(rule)
249
- controller.pub_denied?(:admin, :on => :team)
265
+ context "for a rule" do
266
+ it "should properly build the rule" do
267
+ controller.stub!(:current_person).and_return(Guest.new)
268
+ rule = PermitRule.new :admin, :on => :team
269
+ PermitRule.should_receive(:new).with(:admin, hash_including(:on => :team)).and_return(rule)
270
+ controller.pub_denied?(:admin, :on => :team)
271
+ end
272
+
273
+ it "should properly call #matches? on the rule" do
274
+ bob = Person.create! :name => 'bob'
275
+ controller.stub!(:current_person).and_return(bob)
276
+ rule = PermitRule.new :guest
277
+ rule.should_receive(:matches?).with(bob, instance_of(Binding))
278
+ PermitRule.stub!(:new).and_return(rule)
279
+ controller.pub_denied?(:guest)
280
+ end
281
+
282
+ it "should return the opposite of the rule match" do
283
+ controller.stub!(:current_person).and_return(Guest.new)
284
+ rule = PermitRule.new :guest
285
+ PermitRule.stub!(:new).and_return(rule)
286
+
287
+ rule.stub!(:matches?).and_return(true)
288
+ controller.pub_denied?(:guest).should be_false
289
+
290
+ rule.stub!(:matches?).and_return(false)
291
+ controller.pub_denied?(:guest).should be_true
292
+ end
250
293
  end
251
294
 
252
- it "should properly call #matches? on the rule" do
253
- bob = Person.create! :name => 'bob'
254
- controller.stub!(:current_person).and_return(bob)
255
- rule = PermitRule.new :guest
256
- rule.should_receive(:matches?).with(bob, instance_of(Binding))
257
- PermitRule.stub!(:new).and_return(rule)
258
- controller.pub_denied?(:guest)
295
+ context "for an action on the current controller" do
296
+ it "should evaluate the rules for the action on the current controller" do
297
+ guest = Guest.new
298
+ controller.stub!(:current_person).and_return(guest)
299
+ ProjectsController.permit_rules.should_receive(:permitted?).with(guest, :show, instance_of(Binding)).and_return(true)
300
+ controller.pub_denied?(:action => :show).should be_false
301
+ end
259
302
  end
260
303
 
261
- it "should return the opposite of the rule match" do
262
- controller.stub!(:current_person).and_return(Guest.new)
263
- rule = PermitRule.new :guest
264
- PermitRule.stub!(:new).and_return(rule)
265
-
266
- rule.stub!(:matches?).and_return(true)
267
- controller.pub_denied?(:guest).should be_false
268
-
269
- rule.stub!(:matches?).and_return(false)
270
- controller.pub_denied?(:guest).should be_true
304
+ context "for an action on a different controller" do
305
+ it "should evaluate the rules for the action on the given controller" do
306
+ guest = Guest.new
307
+ controller.stub!(:current_person).and_return(guest)
308
+ TeamsController.permit_rules.should_receive(:permitted?).with(guest, :index, instance_of(Binding)).and_return(true)
309
+ controller.pub_denied?(:controller => 'permit/specs/teams', :action => :index).should be_false
310
+ end
271
311
  end
272
312
  end
273
313
  end