protector 0.3.3 → 0.4.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 96fab27725783cc22165b845a9c4064f116dbe35
4
- data.tar.gz: 7a8dbdbffefcb27380c5c9ff0972a9a2704c1be6
3
+ metadata.gz: bf6b4587e9147a97a18ad6e3b602ca06bf59a07a
4
+ data.tar.gz: 94e454726030e22b403c47d0c80869aa73c97281
5
5
  SHA512:
6
- metadata.gz: 82c3d96e96ad45313add53cb1ef48fb7fa3df78bac3a8566c820a2f3db1a7bbe63fa216685780cf813ad80fd9678d583acd921a8a8a50f56253d2685241ae32c
7
- data.tar.gz: df173a15749af23f7be5e38cf163aa613f42e8750ae2e9fe61a423b3deec66676e9c13c42043775c4539e53572f81f9ce0211fd1fcb2a5517179feed6631ec93
6
+ metadata.gz: 27bdf5f0e506f34c8b779ab62fc9fece5fbb0fb894230b42fc9c2da13d3d874f850ca55a377a28d6a8fec810ee0002525ceed8f3fe2168f71ffb6e807f073d77
7
+ data.tar.gz: eb29c4f00148b9eea6bb980f2a57edc89acd5a6b30bba1f5ee51d81ac7cfb6fffc151e4270a120b66d6a715ec53a553830cdf95eb3f88ac474805bee68579283
data/README.md CHANGED
@@ -98,6 +98,8 @@ Article.where(...).restrict!(current_user)
98
98
 
99
99
  Note that you don't need to explicitly restrict models you get from a restricted scope – they born restricted.
100
100
 
101
+ **Important**: unlike fields, scopes follow black-list approach by default. It means that you will NOT restrict selection in any way if no scope was set within protection block! This arguably is the best default strategy. But it's not the only one – see `paranoid` at the [list of available options](https://github.com/inossidabile/protector#options) for details.
102
+
101
103
  ## Self-aware conditions
102
104
 
103
105
  Sometimes an access decision depends on the object we restrict. `protect` block accepts second argument to fulfill these cases. Keep in mind however that it's not always accessible: we don't have any instance for the restriction of relation and therefore `nil` is passed.
@@ -203,6 +205,14 @@ Protector::Adapters::ActiveRecord.activate!
203
205
 
204
206
  Where "ActiveRecord" is the adapter you are about to use. It can be "Sequel", "DataMapper", "Mongoid".
205
207
 
208
+ ## Options
209
+
210
+ Use `Protector.option = value` to assign an option. Available options are:
211
+
212
+ * **paranoid**: makes scope management white-listed. If set to `true` will force Protector to return empty scope when no scope was given within a protection block.
213
+
214
+ Protector features basic Rails integration so you can assign options using `config.protector.option = value` at your `config/*.rb`.
215
+
206
216
  ## Maintainers
207
217
 
208
218
  * Boris Staal, [@inossidabile](http://staal.io)
@@ -6,6 +6,8 @@ require "protector/dsl"
6
6
  require "protector/adapters/active_record"
7
7
  require "protector/adapters/sequel"
8
8
 
9
+ require "protector/engine" if defined?(Rails)
10
+
9
11
  I18n.load_path += Dir[File.expand_path File.join('..', 'locales', '*.yml'), File.dirname(__FILE__)]
10
12
 
11
13
  module Protector
@@ -28,11 +28,11 @@ module Protector
28
28
  (instance.is_a?(Class) && instance < ActiveRecord::Base)
29
29
  end
30
30
 
31
- def self.nullify(relation)
32
- if modern?
33
- relation.none
31
+ def self.null_proc
32
+ @null_proc ||= if modern?
33
+ Proc.new{ none }
34
34
  else
35
- relation.where("1=0")
35
+ Proc.new{ where("1=0") }
36
36
  end
37
37
  end
38
38
  end
@@ -80,6 +80,7 @@ module Protector
80
80
  # Storage for {Protector::DSL::Meta::Box}
81
81
  def protector_meta
82
82
  @protector_meta ||= self.class.protector_meta.evaluate(
83
+ Protector::Adapters::ActiveRecord,
83
84
  self.class,
84
85
  protector_subject,
85
86
  self.class.column_names,
@@ -31,7 +31,12 @@ module Protector
31
31
  def scope_with_protector(*args)
32
32
  return scope_without_protector unless protector_subject?
33
33
 
34
- @meta ||= klass.protector_meta.evaluate(klass, protector_subject)
34
+ @meta ||= klass.protector_meta.evaluate(
35
+ Protector::Adapters::ActiveRecord,
36
+ klass,
37
+ protector_subject
38
+ )
39
+
35
40
  scope_without_protector.merge(@meta.relation)
36
41
  end
37
42
  end
@@ -28,7 +28,11 @@ module Protector
28
28
  # Gets {Protector::DSL::Meta::Box} of this relation
29
29
  def protector_meta
30
30
  # We don't seem to require columns here as well
31
- @klass.protector_meta.evaluate(@klass, protector_subject)
31
+ @klass.protector_meta.evaluate(
32
+ Protector::Adapters::ActiveRecord,
33
+ @klass,
34
+ protector_subject
35
+ )
32
36
  end
33
37
 
34
38
  # @note Unscoped relation drops properties and therefore should be re-restricted
@@ -107,7 +111,11 @@ module Protector
107
111
  # AR drops default_scope for eagerly loadable associations
108
112
  # https://github.com/inossidabile/protector/issues/3
109
113
  # and so should we
110
- meta = klass.protector_meta.evaluate(klass, subject)
114
+ meta = klass.protector_meta.evaluate(
115
+ Protector::Adapters::ActiveRecord,
116
+ klass,
117
+ subject
118
+ )
111
119
 
112
120
  if meta.scoped?
113
121
  unscoped = klass.unscoped
@@ -20,8 +20,8 @@ module Protector
20
20
  (instance.kind_of?(Class) && instance < ::Sequel::Model)
21
21
  end
22
22
 
23
- def self.nullify(relation)
24
- relation.where("1=0")
23
+ def self.null_proc
24
+ @null_proc ||= Proc.new{ where("1=0") }
25
25
  end
26
26
  end
27
27
  end
@@ -32,7 +32,11 @@ module Protector
32
32
 
33
33
  # Gets {Protector::DSL::Meta::Box} of this dataset
34
34
  def protector_meta
35
- model.protector_meta.evaluate(model, protector_subject)
35
+ model.protector_meta.evaluate(
36
+ Protector::Adapters::Sequel,
37
+ model,
38
+ protector_subject
39
+ )
36
40
  end
37
41
 
38
42
  # Substitutes `row_proc` with {Protector} and injects protection scope
@@ -53,7 +57,11 @@ module Protector
53
57
  @opts[:eager_graph][:reflections].each do |association, reflection|
54
58
  model = reflection[:cache][:class] if reflection[:cache].is_a?(Hash) && reflection[:cache][:class]
55
59
  model = reflection[:class_name].constantize unless model
56
- meta = model.protector_meta.evaluate(model, subject)
60
+ meta = model.protector_meta.evaluate(
61
+ Protector::Adapters::Sequel,
62
+ model,
63
+ subject
64
+ )
57
65
 
58
66
  relation = relation.instance_eval(&meta.scope_proc) if meta.scoped?
59
67
  end
@@ -25,6 +25,7 @@ module Protector
25
25
  # Storage for {Protector::DSL::Meta::Box}
26
26
  def protector_meta
27
27
  @protector_meta ||= self.class.protector_meta.evaluate(
28
+ Protector::Adapters::Sequel,
28
29
  self.class,
29
30
  protector_subject,
30
31
  self.class.columns,
@@ -5,14 +5,15 @@ module Protector
5
5
 
6
6
  # Single DSL evaluation result
7
7
  class Box
8
- attr_accessor :access, :scope_proc, :destroyable
8
+ attr_accessor :adapter, :access, :destroyable
9
9
 
10
10
  # @param model [Class] The class of protected entity
11
11
  # @param fields [Array<String>] All the fields the model has
12
12
  # @param subject [Object] Restriction subject
13
13
  # @param entry [Object] An instance of the model
14
14
  # @param blocks [Array<Proc>] An array of `protect` blocks
15
- def initialize(model, fields, subject, entry, blocks)
15
+ def initialize(adapter, model, fields, subject, entry, blocks)
16
+ @adapter = adapter
16
17
  @model = model
17
18
  @fields = fields
18
19
  @access = {update: {}, view: {}, create: {}}
@@ -36,7 +37,7 @@ module Protector
36
37
  # Checks whether protection with given subject
37
38
  # has the selection scope defined
38
39
  def scoped?
39
- !!@scope_proc
40
+ Protector.paranoid || !!@scope_proc
40
41
  end
41
42
 
42
43
  # @group Protection DSL
@@ -58,13 +59,18 @@ module Protector
58
59
  @unscoped_relation = false
59
60
  end
60
61
 
62
+ def scope_proc
63
+ unless Protector.paranoid
64
+ @scope_proc
65
+ else
66
+ @scope_proc || @adapter.null_proc
67
+ end
68
+ end
69
+
61
70
  def relation
62
71
  return false unless scoped?
63
72
 
64
- unless @relation
65
- @relation = @model.instance_eval(&@scope_proc)
66
- end
67
-
73
+ @relation ||= @model.instance_eval(&scope_proc)
68
74
  @relation
69
75
  end
70
76
 
@@ -212,8 +218,8 @@ module Protector
212
218
  # @param subject [Object] Restriction subject
213
219
  # @param fields [Array<String>] All the fields the model has
214
220
  # @param entry [Object] An instance of the model
215
- def evaluate(model, subject, fields=[], entry=nil)
216
- Box.new(model, fields, subject, entry, blocks)
221
+ def evaluate(adapter, model, subject, fields=[], entry=nil)
222
+ Box.new(adapter, model, fields, subject, entry, blocks)
217
223
  end
218
224
  end
219
225
 
@@ -0,0 +1,11 @@
1
+ module Protector
2
+ class Engine < ::Rails::Engine
3
+ config.protector = ActiveSupport::OrderedOptions.new
4
+
5
+ initializer "protector.configuration" do |app|
6
+ app.config.protector.each do |key, value|
7
+ Protector.send "#{key}=", value
8
+ end
9
+ end
10
+ end
11
+ end
@@ -1,4 +1,4 @@
1
1
  module Protector
2
2
  # Gem version
3
- VERSION = "0.3.3"
3
+ VERSION = "0.4.0"
4
4
  end
@@ -42,6 +42,10 @@ if defined?(ActiveRecord)
42
42
  Protector::Adapters::ActiveRecord.is?(Dummy).should == true
43
43
  Protector::Adapters::ActiveRecord.is?(Dummy.every).should == true
44
44
  end
45
+
46
+ it "sets the adapter" do
47
+ Dummy.restrict!('!').protector_meta.adapter.should == Protector::Adapters::ActiveRecord
48
+ end
45
49
  end
46
50
 
47
51
  #
@@ -87,6 +91,46 @@ if defined?(ActiveRecord)
87
91
  Dummy.restrict!('!').where(number: 999).to_a.first.protector_subject.should == '!'
88
92
  end
89
93
 
94
+ context "with open relation" do
95
+ context "adequate", paranoid: false do
96
+ it "checks existence" do
97
+ Dummy.any?.should == true
98
+ Dummy.restrict!('!').any?.should == true
99
+ end
100
+
101
+ it "counts" do
102
+ Dummy.count.should == 4
103
+ Dummy.restrict!('!').count.should == 4
104
+ end
105
+
106
+ it "fetches" do
107
+ fetched = Dummy.restrict!('!').to_a
108
+
109
+ Dummy.count.should == 4
110
+ fetched.length.should == 4
111
+ end
112
+ end
113
+
114
+ context "paranoid", paranoid: true do
115
+ it "checks existence" do
116
+ Dummy.any?.should == true
117
+ Dummy.restrict!('!').any?.should == false
118
+ end
119
+
120
+ it "counts" do
121
+ Dummy.count.should == 4
122
+ Dummy.restrict!('!').count.should == 0
123
+ end
124
+
125
+ it "fetches" do
126
+ fetched = Dummy.restrict!('!').to_a
127
+
128
+ Dummy.count.should == 4
129
+ fetched.length.should == 0
130
+ end
131
+ end
132
+ end
133
+
90
134
  context "with null relation" do
91
135
  it "checks existence" do
92
136
  Dummy.any?.should == true
@@ -42,6 +42,10 @@ if defined?(Sequel)
42
42
  Protector::Adapters::Sequel.is?(Dummy).should == true
43
43
  Protector::Adapters::Sequel.is?(Dummy.where).should == true
44
44
  end
45
+
46
+ it "sets the adapter" do
47
+ Dummy.restrict!('!').protector_meta.adapter.should == Protector::Adapters::Sequel
48
+ end
45
49
  end
46
50
 
47
51
 
@@ -84,6 +88,54 @@ if defined?(Sequel)
84
88
  Dummy.restrict!('!').eager_graph(fluffies: :loony).all.first.fluffies.first.loony.protector_subject.should == '!'
85
89
  end
86
90
 
91
+ context "with open relation" do
92
+ context "adequate", paranoid: false do
93
+ it "checks existence" do
94
+ Dummy.any?.should == true
95
+ Dummy.restrict!('!').any?.should == true
96
+ end
97
+
98
+ it "counts" do
99
+ Dummy.count.should == 4
100
+ Dummy.restrict!('!').count.should == 4
101
+ end
102
+
103
+ it "fetches first" do
104
+ Dummy.restrict!('!').first.should be_a_kind_of(Dummy)
105
+ end
106
+
107
+ it "fetches all" do
108
+ fetched = Dummy.restrict!('!').to_a
109
+
110
+ Dummy.count.should == 4
111
+ fetched.length.should == 4
112
+ end
113
+ end
114
+
115
+ context "paranoid", paranoid: true do
116
+ it "checks existence" do
117
+ Dummy.any?.should == true
118
+ Dummy.restrict!('!').any?.should == false
119
+ end
120
+
121
+ it "counts" do
122
+ Dummy.count.should == 4
123
+ Dummy.restrict!('!').count.should == 0
124
+ end
125
+
126
+ it "fetches first" do
127
+ Dummy.restrict!('!').first.should == nil
128
+ end
129
+
130
+ it "fetches all" do
131
+ fetched = Dummy.restrict!('!').to_a
132
+
133
+ Dummy.count.should == 4
134
+ fetched.length.should == 0
135
+ end
136
+ end
137
+ end
138
+
87
139
  context "with null relation" do
88
140
  it "checks existence" do
89
141
  Dummy.any?.should == true
@@ -95,10 +147,8 @@ if defined?(Sequel)
95
147
  Dummy.restrict!('-').count.should == 0
96
148
  end
97
149
 
98
- context do
99
- it "fetches first" do
100
- Dummy.restrict!('-').first.should == nil
101
- end
150
+ it "fetches first" do
151
+ Dummy.restrict!('-').first.should == nil
102
152
  end
103
153
 
104
154
  it "fetches all" do
@@ -58,19 +58,22 @@ describe Protector::DSL do
58
58
  @meta = Protector::DSL::Meta.new
59
59
 
60
60
  @meta << lambda {
61
- scope { 'relation' }
61
+ can :view
62
62
  }
63
63
 
64
64
  @meta << lambda {|user|
65
- user.should == 'user'
65
+ scope { 'relation' } if user
66
+ }
67
+
68
+ @meta << lambda {|user|
69
+ user.should == 'user' if user
66
70
 
67
- can :view
68
71
  cannot :view, %w(field5), :field4
69
72
  }
70
73
 
71
74
  @meta << lambda {|user, entry|
72
- user.should == 'user'
73
- entry.should == 'entry'
75
+ user.should == 'user' if user
76
+ entry.should == 'entry' if user
74
77
 
75
78
  can :update, %w(field1 field2 field3),
76
79
  field4: 0..5,
@@ -81,16 +84,30 @@ describe Protector::DSL do
81
84
  end
82
85
 
83
86
  it "evaluates" do
84
- @meta.evaluate(nil, 'user', [], 'entry')
87
+ @meta.evaluate(nil, nil, 'user', [], 'entry')
88
+ end
89
+
90
+ context "adequate", paranoid: false do
91
+ it "sets scoped?" do
92
+ data = @meta.evaluate(nil, nil, nil, [], 'entry')
93
+ data.scoped?.should == false
94
+ end
95
+ end
96
+
97
+ context "paranoid", paranoid: true do
98
+ it "sets scoped?" do
99
+ data = @meta.evaluate(nil, nil, nil, [], 'entry')
100
+ data.scoped?.should == true
101
+ end
85
102
  end
86
103
 
87
104
  it "sets relation" do
88
- data = @meta.evaluate(nil, 'user', [], 'entry')
105
+ data = @meta.evaluate(nil, nil, 'user', [], 'entry')
89
106
  data.relation.should == 'relation'
90
107
  end
91
108
 
92
109
  it "sets access" do
93
- data = @meta.evaluate(nil, 'user', %w(field1 field2 field3 field4 field5), 'entry')
110
+ data = @meta.evaluate(nil, nil, 'user', %w(field1 field2 field3 field4 field5), 'entry')
94
111
  data.access.should == {
95
112
  update: {
96
113
  "field1" => nil,
@@ -109,17 +126,17 @@ describe Protector::DSL do
109
126
  end
110
127
 
111
128
  it "marks destroyable" do
112
- data = @meta.evaluate(nil, 'user', [], 'entry')
129
+ data = @meta.evaluate(nil, nil, 'user', [], 'entry')
113
130
  data.destroyable?.should == true
114
131
  end
115
132
 
116
133
  it "marks updatable" do
117
- data = @meta.evaluate(nil, 'user', [], 'entry')
134
+ data = @meta.evaluate(nil, nil, 'user', [], 'entry')
118
135
  data.updatable?.should == true
119
136
  end
120
137
 
121
138
  it "marks creatable" do
122
- data = @meta.evaluate(nil, 'user', [], 'entry')
139
+ data = @meta.evaluate(nil, nil, 'user', [], 'entry')
123
140
  data.creatable?.should == false
124
141
  end
125
142
  end
@@ -136,13 +153,13 @@ describe Protector::DSL do
136
153
  end
137
154
 
138
155
  it "sets field-level restriction" do
139
- box = @meta.evaluate(nil, 'user', %w(field1 field2), 'entry')
156
+ box = @meta.evaluate(nil, nil, 'user', %w(field1 field2), 'entry')
140
157
  box.can?(:drink, :field1).should == true
141
158
  box.can?(:drink).should == true
142
159
  end
143
160
 
144
161
  it "sets field-level protection" do
145
- box = @meta.evaluate(nil, 'user', %w(field1 field2), 'entry')
162
+ box = @meta.evaluate(nil, nil, 'user', %w(field1 field2), 'entry')
146
163
  box.can?(:eat, :field1).should == false
147
164
  box.can?(:eat).should == true
148
165
  end
@@ -4,6 +4,7 @@ require 'coveralls'
4
4
  Coveralls.wear! if Coveralls.should_run?
5
5
 
6
6
  require 'protector'
7
+ require_relative 'contexts/paranoid'
7
8
  require_relative 'examples/model'
8
9
 
9
10
  RSpec.configure do |config|
@@ -0,0 +1,21 @@
1
+ shared_context "paranoidal", paranoid: true do
2
+ before(:all) do
3
+ @paranoid_condition = Protector.paranoid
4
+ Protector.paranoid = true
5
+ end
6
+
7
+ after(:all) do
8
+ Protector.paranoid = @paranoid_condition
9
+ end
10
+ end
11
+
12
+ shared_context "adequate", paranoid: false do
13
+ before(:all) do
14
+ @paranoid_condition = Protector.paranoid
15
+ Protector.paranoid = false
16
+ end
17
+
18
+ after(:all) do
19
+ Protector.paranoid = @paranoid_condition
20
+ end
21
+ end
@@ -35,8 +35,18 @@ shared_examples_for "a model" do
35
35
  Dummy.first.restrict!('-').visible?.should == false
36
36
  end
37
37
 
38
- it "marks allowed" do
39
- Dummy.first.restrict!('+').visible?.should == true
38
+ context "adequate", paranoid: false do
39
+ it "marks allowed" do
40
+ Dummy.first.restrict!('!').visible?.should == true
41
+ Dummy.first.restrict!('+').visible?.should == true
42
+ end
43
+ end
44
+
45
+ context "paranoid", paranoid: true do
46
+ it "marks allowed" do
47
+ Dummy.first.restrict!('!').visible?.should == false
48
+ Dummy.first.restrict!('+').visible?.should == true
49
+ end
40
50
  end
41
51
  end
42
52
 
@@ -343,21 +353,39 @@ shared_examples_for "a model" do
343
353
  #
344
354
  describe "association" do
345
355
  context "(has_many)" do
346
- it "loads" do
347
- Dummy.first.restrict!('!').fluffies.length.should == 2
348
- Dummy.first.restrict!('+').fluffies.length.should == 1
349
- Dummy.first.restrict!('-').fluffies.empty?.should == true
356
+ context "adequate", paranoid: false do
357
+ it "loads" do
358
+ Dummy.first.restrict!('!').fluffies.length.should == 2
359
+ Dummy.first.restrict!('+').fluffies.length.should == 1
360
+ Dummy.first.restrict!('-').fluffies.empty?.should == true
361
+ end
362
+ end
363
+ context "paranoid", paranoid: true do
364
+ it "loads" do
365
+ Dummy.first.restrict!('!').fluffies.empty?.should == true
366
+ Dummy.first.restrict!('+').fluffies.length.should == 1
367
+ Dummy.first.restrict!('-').fluffies.empty?.should == true
368
+ end
350
369
  end
351
370
  end
352
371
 
353
372
  context "(belongs_to)" do
354
- it "passes subject" do
355
- Fluffy.first.restrict!('!').dummy.protector_subject.should == '!'
373
+ context "adequate", paranoid: false do
374
+ it "passes subject" do
375
+ Fluffy.first.restrict!('!').dummy.protector_subject.should == '!'
376
+ end
377
+
378
+ it "loads" do
379
+ Fluffy.first.restrict!('!').dummy.should be_a_kind_of(Dummy)
380
+ Fluffy.first.restrict!('-').dummy.should == nil
381
+ end
356
382
  end
357
383
 
358
- it "loads" do
359
- Fluffy.first.restrict!('!').dummy.should be_a_kind_of(Dummy)
360
- Fluffy.first.restrict!('-').dummy.should == nil
384
+ context "paranoid", paranoid: true do
385
+ it "loads" do
386
+ Fluffy.first.restrict!('!').dummy.should == nil
387
+ Fluffy.first.restrict!('-').dummy.should == nil
388
+ end
361
389
  end
362
390
  end
363
391
  end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.3.3
4
+ version: 0.4.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boris Staal
@@ -74,6 +74,7 @@ files:
74
74
  - lib/protector/adapters/sequel/eager_graph_loader.rb
75
75
  - lib/protector/adapters/sequel/model.rb
76
76
  - lib/protector/dsl.rb
77
+ - lib/protector/engine.rb
77
78
  - lib/protector/version.rb
78
79
  - locales/en.yml
79
80
  - migrations/active_record.rb
@@ -88,6 +89,7 @@ files:
88
89
  - spec/spec_helpers/adapters/active_record.rb
89
90
  - spec/spec_helpers/adapters/sequel.rb
90
91
  - spec/spec_helpers/boot.rb
92
+ - spec/spec_helpers/contexts/paranoid.rb
91
93
  - spec/spec_helpers/examples/model.rb
92
94
  homepage: https://github.com/inossidabile/protector
93
95
  licenses:
@@ -121,4 +123,5 @@ test_files:
121
123
  - spec/spec_helpers/adapters/active_record.rb
122
124
  - spec/spec_helpers/adapters/sequel.rb
123
125
  - spec/spec_helpers/boot.rb
126
+ - spec/spec_helpers/contexts/paranoid.rb
124
127
  - spec/spec_helpers/examples/model.rb