protector 0.0.4 → 0.1.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: 5196d6460c7c9550f4be2cef9135916d900dcdc0
4
- data.tar.gz: 08886a665e9f57db43dd49d064c23b569e01106b
3
+ metadata.gz: 70b9f27e548ca491e3d9e72033d82ccc6c1ef9a6
4
+ data.tar.gz: 174de9ede88655d225d7281e2d633cfd9c113cd4
5
5
  SHA512:
6
- metadata.gz: 87cd3b93a9b8b3ca0b86d034234385111cd8aa9c793e6440908fc6362a6160e21a7d6010b6c89018a4245a44e1cb043c0086a023cf88ce50e501be7a21cece95
7
- data.tar.gz: 2fffcf64bc2669bfb3292e432e52408d1a9207263085a6cac74dde4200882dc645523c93320449ffe9320f76aa94389caf3ce2c7c993ddfc56333e2426d1d5c8
6
+ metadata.gz: 7f86cb799de05ecb102b5c413492024be29a3d5b27a40e479c7e94e9afcfc7d47f8203e4e62d99f9ed406fa2943880acca7f79449341f3a6fc8ac480b8e59b59
7
+ data.tar.gz: 4864da9f17564ee319ed2faa7891a7a5e30f336bd7a9966ffda20aeae96337e02148ebf196bd0f504ad14f4487ff84b3ede8e7580a8e6f1ac045779ba33176c8
data/Appraisals CHANGED
@@ -1,5 +1,5 @@
1
1
  appraise "AR_3.2" do
2
- gem "activerecord", "3.2", require: "active_record"
2
+ gem "activerecord", "3.2.9", require: "active_record"
3
3
  gem "activerecord-jdbcsqlite3-adapter", platform: :jruby, github: "jruby/activerecord-jdbc-adapter"
4
4
  end
5
5
 
data/README.md CHANGED
@@ -7,7 +7,7 @@ Protector is a Ruby ORM extension for managing security restrictions on a field
7
7
 
8
8
  Currently Protector supports the following ORM adapters:
9
9
 
10
- * [ActiveRecord](http://guides.rubyonrails.org/active_record_querying.html) (>= 3.2)
10
+ * [ActiveRecord](http://guides.rubyonrails.org/active_record_querying.html) (>= 3.2.9)
11
11
 
12
12
  We are working hard to extend the list with:
13
13
 
@@ -111,6 +111,27 @@ Protector is aware of associations. All the associations retrieved from restrict
111
111
 
112
112
  The access to `belongs_to` kind of association depends on corresponding foreign key readability.
113
113
 
114
+ ## Eager Loading
115
+
116
+ To take a long story short: it works and you are very likely to never notice changes it introduces to the process.
117
+
118
+ Eager Loading has 2 possible strategies: JOINs and additional requests. Whenever you mark an association to preload and at the same time use this relation among `where` clause – ORMs prefer JOIN. Otherwise it goes with additional requests.
119
+
120
+ ```ruby
121
+ Foo.includes(:bars) # This will make 2 queries
122
+ Foo.includes(:bars).where(bars: {absolute: true}) # This will make 1 big JOINfull query
123
+ ```
124
+
125
+ The problem here is that JOIN strategy is impossible for scoped restrictions. I.e. for the following code:
126
+
127
+ ```ruby
128
+ Foo.restrict(current_user).includes(:bars).where(bars: {absolute: true})
129
+ ```
130
+
131
+ we can appear in the situation where `foos` and `bars` relations are having different restrictions scopes. In this case JOIN would filter by an intersection of scopes which is wrong.
132
+
133
+ To solve the issue Protector forces additional requests strategy and intelligently adds proper JOINs to the general query to make your conditions work. That's why unlike unrestricted query from the first sample, the code from the second sample will result into 2 queries.
134
+
114
135
  ## Ideology
115
136
 
116
137
  Protector is a successor to [Heimdallr](https://github.com/inossidabile/heimdallr). The latter being a proof-of-concept appeared to be way too paranoid and incompatible with the rest of the world. Protector re-implements same idea keeping the Ruby way:
@@ -10,7 +10,7 @@ gem "guard-rspec"
10
10
  gem "appraisal"
11
11
  gem "sqlite3", :platform=>:ruby
12
12
  gem "jdbc-sqlite3", :platform=>:jruby
13
- gem "activerecord", "3.2", :require=>"active_record"
13
+ gem "activerecord", "3.2.9", :require=>"active_record"
14
14
  gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby, :github=>"jruby/activerecord-jdbc-adapter"
15
15
 
16
16
  gemspec :path=>"../"
@@ -1,31 +1,27 @@
1
1
  GIT
2
2
  remote: git://github.com/jruby/activerecord-jdbc-adapter.git
3
- revision: b1cb2cb59496a7c3ae22799f88c8c4789e6a8cce
3
+ revision: 180dd863d30d6048c914622e4e77f018cf77026a
4
4
  specs:
5
- activerecord-jdbcsqlite3-adapter (1.3.0.DEV)
6
- activerecord-jdbc-adapter (~> 1.3.0.DEV)
7
- jdbc-sqlite3 (~> 3.7.2)
8
5
 
9
6
  PATH
10
7
  remote: /Users/inossidabile/Repos/protector
11
8
  specs:
12
- protector (0.0.3)
9
+ protector (0.1.0)
13
10
  activesupport
14
11
  i18n
15
12
 
16
13
  GEM
17
14
  remote: https://rubygems.org/
18
15
  specs:
19
- activemodel (3.2.0)
20
- activesupport (= 3.2.0)
16
+ activemodel (3.2.9)
17
+ activesupport (= 3.2.9)
21
18
  builder (~> 3.0.0)
22
- activerecord (3.2.0)
23
- activemodel (= 3.2.0)
24
- activesupport (= 3.2.0)
25
- arel (~> 3.0.0)
19
+ activerecord (3.2.9)
20
+ activemodel (= 3.2.9)
21
+ activesupport (= 3.2.9)
22
+ arel (~> 3.0.2)
26
23
  tzinfo (~> 0.3.29)
27
- activerecord-jdbc-adapter (1.3.0.beta1)
28
- activesupport (3.2.0)
24
+ activesupport (3.2.9)
29
25
  i18n (~> 0.6)
30
26
  multi_json (~> 1.0)
31
27
  appraisal (0.5.2)
@@ -36,7 +32,6 @@ GEM
36
32
  coderay (1.0.9)
37
33
  diff-lcs (1.2.4)
38
34
  ffi (1.8.1)
39
- ffi (1.8.1-java)
40
35
  formatador (0.2.4)
41
36
  guard (1.8.0)
42
37
  formatador (>= 0.2.4)
@@ -48,23 +43,17 @@ GEM
48
43
  guard (>= 1.8)
49
44
  rspec (~> 2.13)
50
45
  i18n (0.6.4)
51
- jdbc-sqlite3 (3.7.2)
52
46
  listen (1.1.3)
53
47
  rb-fsevent (>= 0.9.3)
54
48
  rb-inotify (>= 0.9)
55
49
  rb-kqueue (>= 0.2)
56
50
  lumberjack (1.0.3)
57
51
  method_source (0.8.1)
58
- multi_json (1.7.3)
52
+ multi_json (1.7.4)
59
53
  pry (0.9.12.2)
60
54
  coderay (~> 1.0.5)
61
55
  method_source (~> 0.8)
62
56
  slop (~> 3.4)
63
- pry (0.9.12.2-java)
64
- coderay (~> 1.0.5)
65
- method_source (~> 0.8)
66
- slop (~> 3.4)
67
- spoon (~> 0.0)
68
57
  rake (10.0.4)
69
58
  rb-fsevent (0.9.3)
70
59
  rb-inotify (0.9.0)
@@ -80,18 +69,15 @@ GEM
80
69
  diff-lcs (>= 1.1.3, < 2.0)
81
70
  rspec-mocks (2.13.1)
82
71
  slop (3.4.5)
83
- spoon (0.0.4)
84
- ffi
85
72
  sqlite3 (1.3.7)
86
73
  thor (0.18.1)
87
74
  tzinfo (0.3.37)
88
75
 
89
76
  PLATFORMS
90
- java
91
77
  ruby
92
78
 
93
79
  DEPENDENCIES
94
- activerecord (= 3.2)
80
+ activerecord (= 3.2.9)
95
81
  activerecord-jdbcsqlite3-adapter!
96
82
  appraisal
97
83
  guard
@@ -9,7 +9,7 @@ GIT
9
9
  PATH
10
10
  remote: /Users/inossidabile/Repos/protector
11
11
  specs:
12
- protector (0.0.3)
12
+ protector (0.1.0)
13
13
  activesupport
14
14
  i18n
15
15
 
@@ -1,6 +1,7 @@
1
1
  require 'protector/adapters/active_record/base'
2
2
  require 'protector/adapters/active_record/association'
3
3
  require 'protector/adapters/active_record/relation'
4
+ require 'protector/adapters/active_record/preloader'
4
5
 
5
6
  module Protector
6
7
  module Adapters
@@ -10,7 +11,8 @@ module Protector
10
11
  ::ActiveRecord::Relation.send :include, Protector::Adapters::ActiveRecord::Relation
11
12
  ::ActiveRecord::Associations::SingularAssociation.send :include, Protector::Adapters::ActiveRecord::Association
12
13
  ::ActiveRecord::Associations::CollectionAssociation.send :include, Protector::Adapters::ActiveRecord::Association
13
- # ::ActiveRecord::Associations::Preloader::Association.send :include, Protector::Adapters::ActiveRecord::PreloaderAssociation
14
+ ::ActiveRecord::Associations::Preloader.send :include, Protector::Adapters::ActiveRecord::Preloader
15
+ ::ActiveRecord::Associations::Preloader::Association.send :include, Protector::Adapters::ActiveRecord::Preloader::Association
14
16
  end
15
17
  end
16
18
  end
@@ -5,25 +5,18 @@ module Protector
5
5
  extend ActiveSupport::Concern
6
6
 
7
7
  included do
8
- alias_method_chain :scope, :protector
8
+ if method_defined?(:scope)
9
+ alias_method_chain :scope, :protector
10
+ else
11
+ alias_method "scope_without_protector", "scoped"
12
+ alias_method "scoped", "scope_with_protector"
13
+ end
9
14
  end
10
15
 
11
16
  def scope_with_protector(*args)
12
17
  scope_without_protector(*args).restrict!(owner.protector_subject)
13
18
  end
14
19
  end
15
-
16
- module PreloaderAssociation
17
- extend ActiveSupport::Concern
18
-
19
- included do
20
- alias_method_chain :scope, :protector
21
- end
22
-
23
- def scope_with_protector
24
- scope_without_protector
25
- end
26
- end
27
20
  end
28
21
  end
29
22
  end
@@ -46,7 +46,8 @@ module Protector
46
46
  def define_method_attribute(name)
47
47
  super
48
48
 
49
- unless (primary_key == name || (primary_key.is_a?(Array) && primary_key.include?(name)))
49
+ # Show some <3 to composite primary keys
50
+ unless (primary_key == name || Array(primary_key).include?(name))
50
51
  generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
51
52
  alias_method #{"#{name}_unprotected".inspect}, #{name.inspect}
52
53
 
@@ -69,8 +70,8 @@ module Protector
69
70
 
70
71
  self.class.protector_meta.evaluate(
71
72
  self.class,
72
- self.class.column_names,
73
73
  @protector_subject,
74
+ self.class.column_names,
74
75
  self
75
76
  )
76
77
  end
@@ -0,0 +1,32 @@
1
+ module Protector
2
+ module Adapters
3
+ module ActiveRecord
4
+ module Preloader extend ActiveSupport::Concern
5
+
6
+ module Association extend ActiveSupport::Concern
7
+ included do
8
+ if method_defined?(:scope)
9
+ alias_method_chain :scope, :protector
10
+ else
11
+ alias_method "scope_without_protector", "scoped"
12
+ alias_method "scoped", "scope_with_protector"
13
+ end
14
+ end
15
+
16
+ def protector_subject
17
+ # Owners are always loaded from the single source
18
+ # having same protector_subject
19
+ owners.first.protector_subject
20
+ end
21
+
22
+ def scope_with_protector(*args)
23
+ return scope_without_protector unless protector_subject
24
+
25
+ @meta ||= klass.protector_meta.evaluate(klass, protector_subject)
26
+ scope_without_protector.merge(@meta.relation)
27
+ end
28
+ end
29
+ end
30
+ end
31
+ end
32
+ end
@@ -8,10 +8,29 @@ module Protector
8
8
  include Protector::DSL::Base
9
9
 
10
10
  alias_method_chain :exec_queries, :protector
11
+ alias_method_chain :eager_loading?, :protector
12
+
13
+ attr_accessor :eager_loadable_when_protected
14
+
15
+ # AR 3.2 workaround. Come on, guys... SQL parsing :(
16
+ unless method_defined?(:references_values)
17
+ def references_values
18
+ tables_in_string(to_sql)
19
+ end
20
+ end
21
+
22
+ unless method_defined?(:includes!)
23
+ def includes!(*args)
24
+ self.includes_values += args
25
+ self
26
+ end
27
+ end
11
28
  end
12
29
 
13
30
  def protector_meta
14
- @klass.protector_meta.evaluate(@klass, @klass.column_names, @protector_subject)
31
+ # We don't seem to require columns here as well
32
+ # @klass.protector_meta.evaluate(@klass, @protector_subject, @klass.column_names)
33
+ @klass.protector_meta.evaluate(@klass, @protector_subject)
15
34
  end
16
35
 
17
36
  def unscoped
@@ -41,7 +60,119 @@ module Protector
41
60
 
42
61
  subject = @protector_subject
43
62
  relation = merge(protector_meta.relation).unrestrict!
63
+
64
+ relation = protector_substitute_includes(relation)
65
+
66
+ # We should explicitly allow/deny eager loading now that we know
67
+ # if we can use it
68
+ relation.eager_loadable_when_protected = relation.includes_values.any?
69
+
70
+ # Preserve associations from internal loading. We are going to handle that
71
+ # ourselves respecting security scopes FTW!
72
+ associations, relation.preload_values = relation.preload_values, []
73
+
44
74
  @records = relation.send(:exec_queries).each{|r| r.restrict!(subject)}
75
+
76
+ # Now we have @records restricted properly so let's preload associations!
77
+ associations.each do |a|
78
+ ::ActiveRecord::Associations::Preloader.new(@records, a).run
79
+ end
80
+
81
+ @loaded = true
82
+ @records
83
+ end
84
+
85
+ #
86
+ # This method swaps `includes` with `preload` and adds JOINs
87
+ # to any table referenced from `where` (or manually with `reference`)
88
+ #
89
+ def protector_substitute_includes(relation)
90
+ subject = @protector_subject
91
+ includes, relation.includes_values = relation.includes_values, []
92
+
93
+ # We can not allow join-based eager loading for scoped associations
94
+ # since actual filtering can differ for host model and joined relation.
95
+ # Therefore we turn all `includes` into `preloads`.
96
+ #
97
+ # Note that `includes_values` shares reference across relation diffs so
98
+ # it has to be COPIED not modified
99
+ includes.each do |iv|
100
+ protector_expand_include(iv).each do |ive|
101
+ # First-level associations can stay JOINed if restriction scope
102
+ # is absent. Checking deep associations would make us to check
103
+ # every parent. This should probably be done sometimes :\
104
+ meta = ive[0].protector_meta.evaluate(ive[0], subject) unless ive[1].is_a?(Hash)
105
+
106
+ # We leave unscoped restrictions as `includes`
107
+ # but turn scoped ones into `preloads`
108
+ if meta && !meta.scoped?
109
+ relation.includes!(ive[1])
110
+ else
111
+ if relation.references_values.include?(ive[0].table_name)
112
+ if relation.respond_to?(:joins!)
113
+ relation.joins!(ive[1])
114
+ else
115
+ relation = relation.joins(ive[1])
116
+ end
117
+ end
118
+
119
+ # AR 3.2 Y U NO HAVE BANG RELATION MODIFIERS
120
+ relation.preload_values << ive[1]
121
+ false
122
+ end
123
+ end
124
+ end
125
+
126
+ relation
127
+ end
128
+
129
+ #
130
+ # Indexes `includes` format by actual entity class
131
+ # Turns {foo: :bar} into [[Foo, :foo], [Bar, {foo: :bar}]
132
+ #
133
+ def protector_expand_include(inclusion, results=[], base=[], klass=@klass)
134
+ if inclusion.is_a?(Hash)
135
+ protector_expand_include_hash(inclusion, results, base, klass)
136
+ else
137
+ Array(inclusion).each do |i|
138
+ if i.is_a?(Hash)
139
+ protector_expand_include_hash(i, results, base, klass)
140
+ else
141
+ results << [
142
+ klass.reflect_on_association(i.to_sym).klass,
143
+ i.to_sym
144
+ ]
145
+ end
146
+ end
147
+ end
148
+
149
+ results
150
+ end
151
+
152
+ def protector_expand_include_hash(inclusion, results=[], base=[], klass=@klass)
153
+ inclusion.each do |key, value|
154
+ model = klass.reflect_on_association(key.to_sym).klass
155
+ value = [value] unless value.is_a?(Array)
156
+
157
+ value.each do |v|
158
+ if v.is_a?(Hash)
159
+ protector_expand_include_hash(v, results, [key]+base)
160
+ else
161
+ results << [
162
+ model.reflect_on_association(v.to_sym).klass,
163
+ ([key]+base).inject(v){|a, n| { n => a } }
164
+ ]
165
+ end
166
+ end
167
+
168
+ results << [model, base.inject(key){|a, n| { n => a } }]
169
+ end
170
+ end
171
+
172
+ def eager_loading_with_protector?
173
+ flag = eager_loading_without_protector?
174
+ flag &&= !!@eager_loadable_when_protected unless @eager_loadable_when_protected.nil?
175
+ flag
45
176
  end
46
177
  end
47
178
  end
@@ -30,6 +30,10 @@ module Protector
30
30
  @access.each{|k,v| @access_keys[k] = v.keys}
31
31
  end
32
32
 
33
+ def scoped?
34
+ !!@relation
35
+ end
36
+
33
37
  def scope(&block)
34
38
  @relation = @model.instance_eval(&block)
35
39
  end
@@ -112,7 +116,7 @@ module Protector
112
116
  (@blocks ||= []) << block
113
117
  end
114
118
 
115
- def evaluate(model, fields, subject, entry=nil)
119
+ def evaluate(model, subject, fields=[], entry=nil)
116
120
  Box.new(model, fields, subject, entry, @blocks)
117
121
  end
118
122
  end
@@ -1,3 +1,3 @@
1
1
  module Protector
2
- VERSION = "0.0.4"
2
+ VERSION = "0.1.0"
3
3
  end
@@ -3,21 +3,29 @@ require 'spec_helpers/boot'
3
3
  if defined?(ActiveRecord)
4
4
 
5
5
  RSpec::Matchers.define :invalidate do
6
- match do |actual|
7
- actual.save.should == false
8
- actual.errors[:base].should == ["Access denied"]
9
- end
6
+ match do |actual|
7
+ actual.save.should == false
8
+ actual.errors[:base].should == ["Access denied"]
9
+ end
10
10
  end
11
11
 
12
12
  RSpec::Matchers.define :validate do
13
- match do |actual|
14
- actual.class.transaction do
15
- actual.save.should == true
16
- raise ActiveRecord::Rollback
17
- end
13
+ match do |actual|
14
+ actual.class.transaction do
15
+ actual.save.should == true
16
+ raise ActiveRecord::Rollback
17
+ end
18
18
 
19
- true
19
+ true
20
+ end
20
21
  end
22
+
23
+ def log!
24
+ around(:each) do |e|
25
+ ActiveRecord::Base.logger = Logger.new(STDOUT)
26
+ e.run
27
+ ActiveRecord::Base.logger = nil
28
+ end
21
29
  end
22
30
 
23
31
  describe Protector::Adapters::ActiveRecord do
@@ -25,67 +33,82 @@ if defined?(ActiveRecord)
25
33
  ActiveRecord::Schema.verbose = false
26
34
  ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
27
35
 
28
- ActiveRecord::Migration.create_table :dummies do |t|
29
- t.string :string
30
- t.integer :number
31
- t.text :text
32
- t.timestamps
36
+ [:dummies, :fluffies, :bobbies].each do |m|
37
+ ActiveRecord::Migration.create_table m do |t|
38
+ t.string :string
39
+ t.integer :number
40
+ t.text :text
41
+ t.belongs_to :dummy
42
+ t.timestamps
43
+ end
33
44
  end
34
45
 
35
- ActiveRecord::Migration.create_table :fluffies do |t|
36
- t.string :string
37
- t.integer :number
38
- t.belongs_to :dummy
39
- t.timestamps
40
- end
46
+ ActiveRecord::Migration.create_table(:loonies){|t| t.belongs_to :fluffy; t.string :string }
41
47
 
42
48
  Protector::Adapters::ActiveRecord.activate!
43
49
 
44
- class Dummy < ActiveRecord::Base
45
- protect do |x|
46
- scope{ where('1=0') } if x == '-'
47
- scope{ where(number: 999) } if x == '+'
50
+ module Tester extend ActiveSupport::Concern
51
+ included do
52
+ protect do |x|
53
+ scope{ where('1=0') } if x == '-'
54
+ scope{ where(number: 999) } if x == '+'
55
+
56
+ can :view, :dummy_id unless x == '-'
57
+ end
58
+
59
+ scope :none, where('1 = 0') unless respond_to?(:none)
48
60
  end
61
+ end
49
62
 
50
- scope :none, where('1 = 0') unless respond_to?(:none)
63
+ class Dummy < ActiveRecord::Base
64
+ include Tester
51
65
  has_many :fluffies
66
+ has_many :bobbies
52
67
  end
53
- Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
54
- Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
55
68
 
56
69
  class Fluffy < ActiveRecord::Base
57
- protect do |x|
58
- scope{ where('1=0') } if x == '-'
59
- scope{ where(number: 999) } if x == '+'
70
+ include Tester
71
+ belongs_to :dummy
72
+ has_one :loony
73
+ end
60
74
 
61
- can :view, :dummy_id unless x == '-'
62
- end
75
+ class Bobby < ActiveRecord::Base
76
+ protect do; end
77
+ end
63
78
 
64
- scope :none, where('1 = 0') unless respond_to?(:none)
65
- belongs_to :dummy
79
+ class Loony < ActiveRecord::Base
80
+ protect do; end
81
+ end
82
+
83
+ Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
84
+ Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
85
+ Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
86
+ Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
87
+
88
+ [Fluffy, Bobby].each do |m|
89
+ m.create! string: 'zomgstring', number: 999, text: 'zomgtext', dummy_id: 1
90
+ m.create! string: 'zomgstring', number: 777, text: 'zomgtext', dummy_id: 1
91
+ m.create! string: 'zomgstring', number: 999, text: 'zomgtext', dummy_id: 2
92
+ m.create! string: 'zomgstring', number: 777, text: 'zomgtext', dummy_id: 2
66
93
  end
67
- Fluffy.create! string: 'zomgstring', number: 999, dummy_id: 1
68
- Fluffy.create! string: 'zomgstring', number: 777, dummy_id: 1
94
+
95
+ Fluffy.all.each{|f| Loony.create! fluffy_id: f.id, string: 'zomgstring' }
69
96
  end
70
97
 
71
98
  describe Protector::Adapters::ActiveRecord::Base do
72
- before(:each) do
73
- @dummy = Class.new(ActiveRecord::Base) do
99
+ let(:dummy) do
100
+ Class.new(ActiveRecord::Base) do
74
101
  self.table_name = "dummies"
75
102
  scope :none, where('1 = 0') unless respond_to?(:none)
76
103
  end
77
104
  end
78
105
 
79
106
  it "includes" do
80
- @dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
107
+ Dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
81
108
  end
82
109
 
83
110
  it "scopes" do
84
- @dummy.instance_eval do
85
- protect do; scope{ all }; end
86
- end
87
-
88
- scope = @dummy.restrict!('!')
111
+ scope = Dummy.restrict!('!')
89
112
  scope.should be_a_kind_of ActiveRecord::Relation
90
113
  scope.protector_subject.should == '!'
91
114
  end
@@ -93,101 +116,104 @@ if defined?(ActiveRecord)
93
116
  it_behaves_like "a model"
94
117
 
95
118
  describe "eager loading" do
96
- # around(:each) do |e|
97
- # ActiveRecord::Base.logger = Logger.new(STDOUT)
98
- # e.run
99
- # ActiveRecord::Base.logger = nil
100
- # end
101
-
102
119
  it "scopes" do
103
- dummy = Dummy.restrict!('+').includes(:fluffies).first
104
- dummy.fluffies.length.should == 1
120
+ d = Dummy.restrict!('+').includes(:fluffies)
121
+ d.length.should == 2
122
+ d.first.fluffies.length.should == 1
105
123
  end
106
- end
107
- end
108
124
 
109
- describe Protector::Adapters::ActiveRecord::Relation do
110
- before(:all) do
111
- @dummy = Class.new(ActiveRecord::Base) do
112
- self.table_name = "dummies"
113
- scope :none, where('1 = 0') unless respond_to?(:none)
125
+ context "joined to filtered association" do
126
+ it "scopes" do
127
+ d = Dummy.restrict!('+').includes(:fluffies).where(fluffies: {number: 777})
128
+ d.length.should == 2
129
+ d.first.fluffies.length.should == 1
130
+ end
131
+ end
132
+
133
+ context "joined to plain association" do
134
+ it "scopes" do
135
+ d = Dummy.restrict!('+').includes(:bobbies, :fluffies).where(
136
+ bobbies: {number: 777}, fluffies: {number: 777}
137
+ )
138
+ d.length.should == 2
139
+ d.first.fluffies.length.should == 1
140
+ d.first.bobbies.length.should == 1
141
+ end
142
+ end
143
+
144
+ context "with complex include" do
145
+ it "scopes" do
146
+ d = Dummy.restrict!('+').includes(fluffies: :loony).where(
147
+ fluffies: {number: 777},
148
+ loonies: {string: 'zomgstring'}
149
+ )
150
+ d.length.should == 2
151
+ d.first.fluffies.length.should == 1
152
+ d.first.fluffies.first.loony.should be_a_kind_of(Loony)
153
+ end
114
154
  end
115
155
  end
156
+ end
116
157
 
158
+ describe Protector::Adapters::ActiveRecord::Relation do
117
159
  it "includes" do
118
- @dummy.none.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
160
+ Dummy.none.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
119
161
  end
120
162
 
121
163
  it "saves subject" do
122
- @dummy.restrict!('!').where(number: 999).protector_subject.should == '!'
164
+ Dummy.restrict!('!').where(number: 999).protector_subject.should == '!'
123
165
  end
124
166
 
125
167
  it "forwards subject" do
126
- @dummy.instance_eval do
127
- protect do; end
128
- end
129
-
130
- @dummy.restrict!('!').where(number: 999).first.protector_subject.should == '!'
131
- @dummy.restrict!('!').where(number: 999).to_a.first.protector_subject.should == '!'
168
+ Dummy.restrict!('!').where(number: 999).first.protector_subject.should == '!'
169
+ Dummy.restrict!('!').where(number: 999).to_a.first.protector_subject.should == '!'
132
170
  end
133
171
 
134
172
  context "with null relation" do
135
- before(:each) do
136
- @dummy.instance_eval do
137
- protect do; scope{ none }; end
138
- end
139
- end
140
-
141
173
  it "checks existence" do
142
- @dummy.any?.should == true
143
- @dummy.restrict!('!').any?.should == false
174
+ Dummy.any?.should == true
175
+ Dummy.restrict!('-').any?.should == false
144
176
  end
145
177
 
146
178
  it "counts" do
147
- @dummy.count.should == 2
148
- @dummy.restrict!('!').count.should == 0
179
+ Dummy.count.should == 4
180
+ Dummy.restrict!('-').count.should == 0
149
181
  end
150
182
 
151
183
  it "fetches" do
152
- fetched = @dummy.restrict!('!').to_a
184
+ fetched = Dummy.restrict!('-').to_a
153
185
 
154
- @dummy.all.length.should == 2
186
+ Dummy.count.should == 4
155
187
  fetched.length.should == 0
156
188
  end
157
189
 
158
190
  it "keeps security scope when unscoped" do
159
- @dummy.unscoped.restrict!('!').count.should == 0
160
- @dummy.restrict!('!').unscoped.count.should == 0
191
+ Dummy.unscoped.restrict!('-').count.should == 0
192
+ Dummy.restrict!('-').unscoped.count.should == 0
161
193
  end
162
194
  end
163
195
 
164
196
  context "with active relation" do
165
- before(:each) do
166
- @dummy.instance_eval do
167
- protect do; scope{ where(number: 999) }; end
168
- end
169
- end
170
-
171
197
  it "checks existence" do
172
- @dummy.any?.should == true
173
- @dummy.restrict!('!').any?.should == true
198
+ Dummy.any?.should == true
199
+ Dummy.restrict!('+').any?.should == true
174
200
  end
175
201
 
176
202
  it "counts" do
177
- @dummy.count.should == 2
178
- @dummy.restrict!('!').count.should == 1
203
+ Dummy.count.should == 4
204
+ Dummy.restrict!('+').count.should == 2
179
205
  end
180
206
 
181
207
  it "fetches" do
182
- fetched = @dummy.restrict!('!').to_a
208
+ fetched = Dummy.restrict!('+').to_a
183
209
 
184
- @dummy.all.length.should == 2
185
- fetched.length.should == 1
210
+ Dummy.count.should == 4
211
+ fetched.length.should == 2
186
212
  end
187
213
 
188
214
  it "keeps security scope when unscoped" do
189
- @dummy.unscoped.restrict!('!').count.should == 1
190
- @dummy.restrict!('!').unscoped.count.should == 1
215
+ Dummy.unscoped.restrict!('+').count.should == 2
216
+ Dummy.restrict!('+').unscoped.count.should == 2
191
217
  end
192
218
  end
193
219
  end
@@ -70,16 +70,16 @@ describe Protector::DSL do
70
70
  end
71
71
 
72
72
  it "evaluates" do
73
- @meta.evaluate(nil, [], 'user', 'entry')
73
+ @meta.evaluate(nil, 'user', [], 'entry')
74
74
  end
75
75
 
76
76
  it "sets relation" do
77
- data = @meta.evaluate(nil, [], 'user', 'entry')
77
+ data = @meta.evaluate(nil, 'user', [], 'entry')
78
78
  data.relation.should == 'relation'
79
79
  end
80
80
 
81
81
  it "sets access" do
82
- data = @meta.evaluate(nil, %w(field1 field2 field3 field4 field5), 'user', 'entry')
82
+ data = @meta.evaluate(nil, 'user', %w(field1 field2 field3 field4 field5), 'entry')
83
83
  data.access.should == {
84
84
  "update" => {
85
85
  "field1" => nil,
@@ -98,17 +98,17 @@ describe Protector::DSL do
98
98
  end
99
99
 
100
100
  it "marks destroyable" do
101
- data = @meta.evaluate(nil, [], 'user', 'entry')
101
+ data = @meta.evaluate(nil, 'user', [], 'entry')
102
102
  data.destroyable?.should == true
103
103
  end
104
104
 
105
105
  it "marks updatable" do
106
- data = @meta.evaluate(nil, [], 'user', 'entry')
106
+ data = @meta.evaluate(nil, 'user', [], 'entry')
107
107
  data.updatable?.should == true
108
108
  end
109
109
 
110
110
  it "marks creatable" do
111
- data = @meta.evaluate(nil, [], 'user', 'entry')
111
+ data = @meta.evaluate(nil, 'user', [], 'entry')
112
112
  data.creatable?.should == false
113
113
  end
114
114
  end
@@ -1,6 +1,6 @@
1
1
  shared_examples_for "a model" do
2
2
  it "evaluates meta properly" do
3
- @dummy.instance_eval do
3
+ dummy.instance_eval do
4
4
  protect do |subject, dummy|
5
5
  subject.should == '!'
6
6
 
@@ -12,9 +12,8 @@ shared_examples_for "a model" do
12
12
  end
13
13
  end
14
14
 
15
- fields = Hash[*%w(id string number text created_at updated_at).map{|x| [x, nil]}.flatten]
16
- dummy = @dummy.new.restrict!('!')
17
- meta = dummy.protector_meta
15
+ fields = Hash[*%w(id string number text dummy_id created_at updated_at).map{|x| [x, nil]}.flatten]
16
+ meta = dummy.new.restrict!('!').protector_meta
18
17
 
19
18
  meta.access[:view].should == fields
20
19
  meta.access[:create].should == fields
@@ -23,11 +22,6 @@ shared_examples_for "a model" do
23
22
 
24
23
  describe "association" do
25
24
  context "(has_many)" do
26
- around(:each) do |e|
27
- ActiveRecord::Base.logger = Logger.new(STDOUT)
28
- e.run
29
- ActiveRecord::Base.logger = nil
30
- end
31
25
  it "loads" do
32
26
  Dummy.first.restrict!('!').fluffies.length.should == 2
33
27
  Dummy.first.restrict!('+').fluffies.length.should == 1
@@ -49,60 +43,52 @@ shared_examples_for "a model" do
49
43
 
50
44
  describe "visibility" do
51
45
  it "marks blocked" do
52
- @dummy.instance_eval do
53
- protect do; scope { none }; end
54
- end
55
-
56
- @dummy.first.restrict!('!').visible?.should == false
46
+ Dummy.first.restrict!('-').visible?.should == false
57
47
  end
58
48
 
59
49
  it "marks allowed" do
60
- @dummy.instance_eval do
61
- protect do; scope { limit(5) }; end
62
- end
63
-
64
- @dummy.first.restrict!('!').visible?.should == true
50
+ Dummy.first.restrict!('+').visible?.should == true
65
51
  end
66
52
  end
67
53
 
68
54
  describe "readability" do
69
55
  it "hides fields" do
70
- @dummy.instance_eval do
56
+ dummy.instance_eval do
71
57
  protect do
72
58
  can :view, :string
73
59
  end
74
60
  end
75
61
 
76
- dummy = @dummy.first.restrict!('!')
77
- dummy.number.should == nil
78
- dummy[:number].should == nil
79
- dummy.read_attribute(:number).should_not == nil
80
- dummy.string.should == 'zomgstring'
62
+ d = dummy.first.restrict!('!')
63
+ d.number.should == nil
64
+ d[:number].should == nil
65
+ d.read_attribute(:number).should_not == nil
66
+ d.string.should == 'zomgstring'
81
67
  end
82
68
  end
83
69
 
84
70
  describe "creatability" do
85
71
  context "with empty meta" do
86
72
  before(:each) do
87
- @dummy.instance_eval do
73
+ dummy.instance_eval do
88
74
  protect do; end
89
75
  end
90
76
  end
91
77
 
92
78
  it "marks blocked" do
93
- dummy = @dummy.new(string: 'bam', number: 1)
94
- dummy.restrict!('!').creatable?.should == false
79
+ d = dummy.new(string: 'bam', number: 1)
80
+ d.restrict!('!').creatable?.should == false
95
81
  end
96
82
 
97
83
  it "invalidates" do
98
- dummy = @dummy.new(string: 'bam', number: 1).restrict!('!')
99
- dummy.should invalidate
84
+ d = dummy.new(string: 'bam', number: 1).restrict!('!')
85
+ d.should invalidate
100
86
  end
101
87
  end
102
88
 
103
89
  context "by list of fields" do
104
90
  before(:each) do
105
- @dummy.instance_eval do
91
+ dummy.instance_eval do
106
92
  protect do
107
93
  can :create, :string
108
94
  end
@@ -110,29 +96,29 @@ shared_examples_for "a model" do
110
96
  end
111
97
 
112
98
  it "marks blocked" do
113
- dummy = @dummy.new(string: 'bam', number: 1)
114
- dummy.restrict!('!').creatable?.should == false
99
+ d = dummy.new(string: 'bam', number: 1)
100
+ d.restrict!('!').creatable?.should == false
115
101
  end
116
102
 
117
103
  it "marks allowed" do
118
- dummy = @dummy.new(string: 'bam')
119
- dummy.restrict!('!').creatable?.should == true
104
+ d = dummy.new(string: 'bam')
105
+ d.restrict!('!').creatable?.should == true
120
106
  end
121
107
 
122
108
  it "invalidates" do
123
- dummy = @dummy.new(string: 'bam', number: 1).restrict!('!')
124
- dummy.should invalidate
109
+ d = dummy.new(string: 'bam', number: 1).restrict!('!')
110
+ d.should invalidate
125
111
  end
126
112
 
127
113
  it "validates" do
128
- dummy = @dummy.new(string: 'bam').restrict!('!')
129
- dummy.should validate
114
+ d = dummy.new(string: 'bam').restrict!('!')
115
+ d.should validate
130
116
  end
131
117
  end
132
118
 
133
119
  context "by lambdas" do
134
120
  before(:each) do
135
- @dummy.instance_eval do
121
+ dummy.instance_eval do
136
122
  protect do
137
123
  can :create, string: lambda {|x| x.try(:length) == 5 }
138
124
  end
@@ -140,29 +126,29 @@ shared_examples_for "a model" do
140
126
  end
141
127
 
142
128
  it "marks blocked" do
143
- dummy = @dummy.new(string: 'bam')
144
- dummy.restrict!('!').creatable?.should == false
129
+ d = dummy.new(string: 'bam')
130
+ d.restrict!('!').creatable?.should == false
145
131
  end
146
132
 
147
133
  it "marks allowed" do
148
- dummy = @dummy.new(string: '12345')
149
- dummy.restrict!('!').creatable?.should == true
134
+ d = dummy.new(string: '12345')
135
+ d.restrict!('!').creatable?.should == true
150
136
  end
151
137
 
152
138
  it "invalidates" do
153
- dummy = @dummy.new(string: 'bam').restrict!('!')
154
- dummy.should invalidate
139
+ d = dummy.new(string: 'bam').restrict!('!')
140
+ d.should invalidate
155
141
  end
156
142
 
157
143
  it "validates" do
158
- dummy = @dummy.new(string: '12345').restrict!('!')
159
- dummy.should validate
144
+ d = dummy.new(string: '12345').restrict!('!')
145
+ d.should validate
160
146
  end
161
147
  end
162
148
 
163
149
  context "by ranges" do
164
150
  before(:each) do
165
- @dummy.instance_eval do
151
+ dummy.instance_eval do
166
152
  protect do
167
153
  can :create, number: 0..2
168
154
  end
@@ -170,23 +156,23 @@ shared_examples_for "a model" do
170
156
  end
171
157
 
172
158
  it "marks blocked" do
173
- dummy = @dummy.new(number: 500)
174
- dummy.restrict!('!').creatable?.should == false
159
+ d = dummy.new(number: 500)
160
+ d.restrict!('!').creatable?.should == false
175
161
  end
176
162
 
177
163
  it "marks allowed" do
178
- dummy = @dummy.new(number: 2)
179
- dummy.restrict!('!').creatable?.should == true
164
+ d = dummy.new(number: 2)
165
+ d.restrict!('!').creatable?.should == true
180
166
  end
181
167
 
182
168
  it "invalidates" do
183
- dummy = @dummy.new(number: 500).restrict!('!')
184
- dummy.should invalidate
169
+ d = dummy.new(number: 500).restrict!('!')
170
+ d.should invalidate
185
171
  end
186
172
 
187
173
  it "validates" do
188
- dummy = @dummy.new(number: 2).restrict!('!')
189
- dummy.should validate
174
+ d = dummy.new(number: 2).restrict!('!')
175
+ d.should validate
190
176
  end
191
177
  end
192
178
  end
@@ -194,27 +180,27 @@ shared_examples_for "a model" do
194
180
  describe "updatability" do
195
181
  context "with empty meta" do
196
182
  before(:each) do
197
- @dummy.instance_eval do
183
+ dummy.instance_eval do
198
184
  protect do; end
199
185
  end
200
186
  end
201
187
 
202
188
  it "marks blocked" do
203
- dummy = @dummy.first
204
- dummy.assign_attributes(string: 'bam', number: 1)
205
- dummy.restrict!('!').updatable?.should == false
189
+ d = dummy.first
190
+ d.assign_attributes(string: 'bam', number: 1)
191
+ d.restrict!('!').updatable?.should == false
206
192
  end
207
193
 
208
194
  it "invalidates" do
209
- dummy = @dummy.first.restrict!('!')
210
- dummy.assign_attributes(string: 'bam', number: 1)
211
- dummy.should invalidate
195
+ d = dummy.first.restrict!('!')
196
+ d.assign_attributes(string: 'bam', number: 1)
197
+ d.should invalidate
212
198
  end
213
199
  end
214
200
 
215
201
  context "by list of fields" do
216
202
  before(:each) do
217
- @dummy.instance_eval do
203
+ dummy.instance_eval do
218
204
  protect do
219
205
  can :update, :string
220
206
  end
@@ -222,33 +208,33 @@ shared_examples_for "a model" do
222
208
  end
223
209
 
224
210
  it "marks blocked" do
225
- dummy = @dummy.first
226
- dummy.assign_attributes(string: 'bam', number: 1)
227
- dummy.restrict!('!').updatable?.should == false
211
+ d = dummy.first
212
+ d.assign_attributes(string: 'bam', number: 1)
213
+ d.restrict!('!').updatable?.should == false
228
214
  end
229
215
 
230
216
  it "marks allowed" do
231
- dummy = @dummy.first
232
- dummy.assign_attributes(string: 'bam')
233
- dummy.restrict!('!').updatable?.should == true
217
+ d = dummy.first
218
+ d.assign_attributes(string: 'bam')
219
+ d.restrict!('!').updatable?.should == true
234
220
  end
235
221
 
236
222
  it "invalidates" do
237
- dummy = @dummy.first.restrict!('!')
238
- dummy.assign_attributes(string: 'bam', number: 1)
239
- dummy.should invalidate
223
+ d = dummy.first.restrict!('!')
224
+ d.assign_attributes(string: 'bam', number: 1)
225
+ d.should invalidate
240
226
  end
241
227
 
242
228
  it "validates" do
243
- dummy = @dummy.first.restrict!('!')
244
- dummy.assign_attributes(string: 'bam')
245
- dummy.should validate
229
+ d = dummy.first.restrict!('!')
230
+ d.assign_attributes(string: 'bam')
231
+ d.should validate
246
232
  end
247
233
  end
248
234
 
249
235
  context "by lambdas" do
250
236
  before(:each) do
251
- @dummy.instance_eval do
237
+ dummy.instance_eval do
252
238
  protect do
253
239
  can :update, string: lambda {|x| x.try(:length) == 5 }
254
240
  end
@@ -256,33 +242,33 @@ shared_examples_for "a model" do
256
242
  end
257
243
 
258
244
  it "marks blocked" do
259
- dummy = @dummy.first
260
- dummy.assign_attributes(string: 'bam')
261
- dummy.restrict!('!').updatable?.should == false
245
+ d = dummy.first
246
+ d.assign_attributes(string: 'bam')
247
+ d.restrict!('!').updatable?.should == false
262
248
  end
263
249
 
264
250
  it "marks allowed" do
265
- dummy = @dummy.first
266
- dummy.assign_attributes(string: '12345')
267
- dummy.restrict!('!').updatable?.should == true
251
+ d = dummy.first
252
+ d.assign_attributes(string: '12345')
253
+ d.restrict!('!').updatable?.should == true
268
254
  end
269
255
 
270
256
  it "invalidates" do
271
- dummy = @dummy.first.restrict!('!')
272
- dummy.assign_attributes(string: 'bam')
273
- dummy.should invalidate
257
+ d = dummy.first.restrict!('!')
258
+ d.assign_attributes(string: 'bam')
259
+ d.should invalidate
274
260
  end
275
261
 
276
262
  it "validates" do
277
- dummy = @dummy.first.restrict!('!')
278
- dummy.assign_attributes(string: '12345')
279
- dummy.should validate
263
+ d = dummy.first.restrict!('!')
264
+ d.assign_attributes(string: '12345')
265
+ d.should validate
280
266
  end
281
267
  end
282
268
 
283
269
  context "by ranges" do
284
270
  before(:each) do
285
- @dummy.instance_eval do
271
+ dummy.instance_eval do
286
272
  protect do
287
273
  can :update, number: 0..2
288
274
  end
@@ -290,64 +276,64 @@ shared_examples_for "a model" do
290
276
  end
291
277
 
292
278
  it "marks blocked" do
293
- dummy = @dummy.first
294
- dummy.assign_attributes(number: 500)
295
- dummy.restrict!('!').updatable?.should == false
279
+ d = dummy.first
280
+ d.assign_attributes(number: 500)
281
+ d.restrict!('!').updatable?.should == false
296
282
  end
297
283
 
298
284
  it "marks allowed" do
299
- dummy = @dummy.first
300
- dummy.assign_attributes(number: 2)
301
- dummy.restrict!('!').updatable?.should == true
285
+ d = dummy.first
286
+ d.assign_attributes(number: 2)
287
+ d.restrict!('!').updatable?.should == true
302
288
  end
303
289
 
304
290
  it "invalidates" do
305
- dummy = @dummy.first.restrict!('!')
306
- dummy.assign_attributes(number: 500)
307
- dummy.should invalidate
291
+ d = dummy.first.restrict!('!')
292
+ d.assign_attributes(number: 500)
293
+ d.should invalidate
308
294
  end
309
295
 
310
296
  it "validates" do
311
- dummy = @dummy.first.restrict!('!')
312
- dummy.assign_attributes(number: 2)
313
- dummy.should validate
297
+ d = dummy.first.restrict!('!')
298
+ d.assign_attributes(number: 2)
299
+ d.should validate
314
300
  end
315
301
  end
316
302
  end
317
303
 
318
304
  describe "destroyability" do
319
305
  it "marks blocked" do
320
- @dummy.instance_eval do
306
+ dummy.instance_eval do
321
307
  protect do; end
322
308
  end
323
309
 
324
- @dummy.first.restrict!('!').destroyable?.should == false
310
+ dummy.first.restrict!('!').destroyable?.should == false
325
311
  end
326
312
 
327
313
  it "marks allowed" do
328
- @dummy.instance_eval do
314
+ dummy.instance_eval do
329
315
  protect do; can :destroy; end
330
316
  end
331
317
 
332
- @dummy.first.restrict!('!').destroyable?.should == true
318
+ dummy.first.restrict!('!').destroyable?.should == true
333
319
  end
334
320
 
335
321
  it "invalidates" do
336
- @dummy.instance_eval do
322
+ dummy.instance_eval do
337
323
  protect do; end
338
324
  end
339
325
 
340
- @dummy.first.restrict!('!').destroy.should == false
326
+ dummy.first.restrict!('!').destroy.should == false
341
327
  end
342
328
 
343
329
  it "validates" do
344
- @dummy.instance_eval do
330
+ dummy.instance_eval do
345
331
  protect do; can :destroy; end
346
332
  end
347
333
 
348
- dummy = @dummy.create!.restrict!('!')
349
- dummy.destroy.should == dummy
350
- dummy.destroyed?.should == true
334
+ d = dummy.create!.restrict!('!')
335
+ d.destroy.should == d
336
+ d.destroyed?.should == true
351
337
  end
352
338
  end
353
339
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: protector
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.4
4
+ version: 0.1.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Boris Staal
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-05-28 00:00:00.000000000 Z
11
+ date: 2013-05-29 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: activesupport
@@ -62,6 +62,7 @@ files:
62
62
  - lib/protector/adapters/active_record.rb
63
63
  - lib/protector/adapters/active_record/association.rb
64
64
  - lib/protector/adapters/active_record/base.rb
65
+ - lib/protector/adapters/active_record/preloader.rb
65
66
  - lib/protector/adapters/active_record/relation.rb
66
67
  - lib/protector/dsl.rb
67
68
  - lib/protector/version.rb