permit 0.9.0 → 1.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.
@@ -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