protector 0.0.2 → 0.0.4

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,3 +1,7 @@
1
+ require 'protector/adapters/active_record/base'
2
+ require 'protector/adapters/active_record/association'
3
+ require 'protector/adapters/active_record/relation'
4
+
1
5
  module Protector
2
6
  module Adapters
3
7
  module ActiveRecord
@@ -6,151 +10,7 @@ module Protector
6
10
  ::ActiveRecord::Relation.send :include, Protector::Adapters::ActiveRecord::Relation
7
11
  ::ActiveRecord::Associations::SingularAssociation.send :include, Protector::Adapters::ActiveRecord::Association
8
12
  ::ActiveRecord::Associations::CollectionAssociation.send :include, Protector::Adapters::ActiveRecord::Association
9
- end
10
-
11
- module Base
12
- extend ActiveSupport::Concern
13
-
14
- included do
15
- include Protector::DSL::Base
16
- include Protector::DSL::Entry
17
-
18
- validate(on: :create) do
19
- return unless @protector_subject
20
- errors[:base] << I18n.t('protector.invalid') unless creatable?
21
- end
22
-
23
- validate(on: :update) do
24
- return unless @protector_subject
25
- errors[:base] << I18n.t('protector.invalid') unless updatable?
26
- end
27
-
28
- before_destroy do
29
- return true unless @protector_subject
30
- destroyable?
31
- end
32
- end
33
-
34
- module ClassMethods
35
- def restrict(subject)
36
- all.restrict(subject)
37
- end
38
-
39
- def define_method_attribute(name)
40
- safe_name = name.unpack('h*').first
41
-
42
- if primary_key == name || (primary_key.is_a?(Array) && primary_key.include?(name))
43
- condition = "true"
44
- else
45
- condition = "!@protector_subject || protector_meta.readable?(#{name.inspect})"
46
- end
47
-
48
- generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
49
- def __temp__#{safe_name}
50
- if #{condition}
51
- read_attribute(AttrNames::ATTR_#{safe_name}) { |n| missing_attribute(n, caller) }
52
- else
53
- nil
54
- end
55
- end
56
- alias_method #{name.inspect}, :__temp__#{safe_name}
57
- undef_method :__temp__#{safe_name}
58
- STR
59
- end
60
- end
61
-
62
- def protector_meta
63
- unless @protector_subject
64
- raise "Unprotected entity detected: use `restrict` method to protect it."
65
- end
66
-
67
- self.class.protector_meta.evaluate(
68
- self.class,
69
- self.class.column_names,
70
- @protector_subject,
71
- self
72
- )
73
- end
74
-
75
- def visible?
76
- protector_meta.relation.where(
77
- self.class.primary_key => id
78
- ).any?
79
- end
80
-
81
- def creatable?
82
- fields = HashWithIndifferentAccess[changed.map{|x| [x, read_attribute(x)]}]
83
- protector_meta.creatable?(fields)
84
- end
85
-
86
- def updatable?
87
- fields = HashWithIndifferentAccess[changed.map{|x| [x, read_attribute(x)]}]
88
- protector_meta.updatable?(fields)
89
- end
90
-
91
- def destroyable?
92
- protector_meta.destroyable?
93
- end
94
-
95
- def [](name)
96
- if !@protector_subject || protector_meta.readable?(name)
97
- super
98
- else
99
- nil
100
- end
101
- end
102
-
103
- def association(*args)
104
- association = super
105
- association.restrict @protector_subject
106
- association
107
- end
108
- end
109
-
110
- module Relation
111
- extend ActiveSupport::Concern
112
-
113
- included do
114
- include Protector::DSL::Base
115
-
116
- alias_method_chain :exec_queries, :protector
117
- end
118
-
119
- def protector_meta
120
- @klass.protector_meta.evaluate(@klass, @klass.column_names, @protector_subject)
121
- end
122
-
123
- def count
124
- super || 0
125
- end
126
-
127
- def sum
128
- super || 0
129
- end
130
-
131
- def calculate(*args)
132
- return super unless @protector_subject
133
- merge(protector_meta.relation).unrestrict.calculate *args
134
- end
135
-
136
- def exec_queries_with_protector(*args)
137
- return exec_queries_without_protector unless @protector_subject
138
- @records = merge(protector_meta.relation).unrestrict.send :exec_queries
139
- end
140
- end
141
-
142
- module Association
143
- extend ActiveSupport::Concern
144
-
145
- included do
146
- include Protector::DSL::Base
147
-
148
- alias_method_chain :reader, :protector
149
- end
150
-
151
- def reader_with_protector
152
- reader_without_protector.restrict(@protector_subject)
153
- end
13
+ # ::ActiveRecord::Associations::Preloader::Association.send :include, Protector::Adapters::ActiveRecord::PreloaderAssociation
154
14
  end
155
15
  end
156
16
  end
data/lib/protector/dsl.rb CHANGED
@@ -2,6 +2,9 @@ module Protector
2
2
  module DSL
3
3
  # DSL meta storage and evaluator
4
4
  class Meta
5
+
6
+ # Evaluation result moved out of Meta to make it thread-safe
7
+ # and incapsulate better
5
8
  class Box
6
9
  attr_accessor :access, :relation, :destroyable
7
10
 
@@ -14,9 +17,10 @@ module Protector
14
17
  @destroyable = false
15
18
 
16
19
  blocks.each do |b|
17
- if b.arity == 2
20
+ case b.arity
21
+ when 2
18
22
  instance_exec subject, entry, &b
19
- elsif b.arity == 1
23
+ when 1
20
24
  instance_exec subject, &b
21
25
  else
22
26
  instance_exec &b
@@ -120,12 +124,12 @@ module Protector
120
124
  attr_reader :protector_subject
121
125
  end
122
126
 
123
- def restrict(subject)
127
+ def restrict!(subject)
124
128
  @protector_subject = subject
125
129
  self
126
130
  end
127
131
 
128
- def unrestrict
132
+ def unrestrict!
129
133
  @protector_subject = nil
130
134
  self
131
135
  end
@@ -1,3 +1,3 @@
1
1
  module Protector
2
- VERSION = "0.0.2"
2
+ VERSION = "0.0.4"
3
3
  end
data/lib/protector.rb CHANGED
@@ -1,4 +1,5 @@
1
1
  require "active_support/all"
2
+ require "i18n"
2
3
 
3
4
  require "protector/version"
4
5
  require "protector/dsl"
@@ -6,8 +7,4 @@ require "protector/adapters/active_record"
6
7
 
7
8
  I18n.load_path << Dir[File.join File.expand_path(File.dirname(__FILE__)), '..', 'locales', '*.yml']
8
9
 
9
- Protector::Adapters::ActiveRecord.activate! if defined?(ActiveRecord)
10
-
11
- module Protector
12
- # Your code goes here...
13
- end
10
+ Protector::Adapters::ActiveRecord.activate! if defined?(ActiveRecord)
@@ -1,118 +1,196 @@
1
1
  require 'spec_helpers/boot'
2
2
 
3
- describe Protector::Adapters::ActiveRecord do
4
- before(:all) do
5
- ActiveRecord::Schema.verbose = false
6
- ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
7
-
8
- ActiveRecord::Migration.create_table :dummies do |t|
9
- t.string :string
10
- t.integer :number
11
- t.text :text
12
- t.timestamps
13
- end
14
-
15
- ActiveRecord::Migration.create_table :fluffies do |t|
16
- t.string :string
17
- t.belongs_to :dummy
18
- t.timestamps
19
- end
3
+ if defined?(ActiveRecord)
20
4
 
21
- Protector::Adapters::ActiveRecord.activate!
5
+ RSpec::Matchers.define :invalidate do
6
+ match do |actual|
7
+ actual.save.should == false
8
+ actual.errors[:base].should == ["Access denied"]
9
+ end
10
+ end
22
11
 
23
- class Dummy < ActiveRecord::Base
24
- protect do; end
25
- has_many :fluffies
12
+ RSpec::Matchers.define :validate do
13
+ match do |actual|
14
+ actual.class.transaction do
15
+ actual.save.should == true
16
+ raise ActiveRecord::Rollback
26
17
  end
27
- Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
28
- Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
29
18
 
30
- class Fluffy < ActiveRecord::Base
31
- protect do
32
- can :view, :dummy_id
33
- end
34
- belongs_to :dummy
35
- end
36
- Fluffy.create! string: 'zomgstring', dummy_id: 1
19
+ true
20
+ end
37
21
  end
38
22
 
39
- describe Protector::Adapters::ActiveRecord::Base do
40
- before(:each) do
41
- @dummy = Class.new(ActiveRecord::Base) do
42
- self.table_name = "dummies"
23
+ describe Protector::Adapters::ActiveRecord do
24
+ before(:all) do
25
+ ActiveRecord::Schema.verbose = false
26
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
27
+
28
+ ActiveRecord::Migration.create_table :dummies do |t|
29
+ t.string :string
30
+ t.integer :number
31
+ t.text :text
32
+ t.timestamps
43
33
  end
44
- end
45
-
46
- it "includes" do
47
- @dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
48
- end
49
34
 
50
- it "scopes" do
51
- @dummy.instance_eval do
52
- protect do; scope{ all }; end
35
+ ActiveRecord::Migration.create_table :fluffies do |t|
36
+ t.string :string
37
+ t.integer :number
38
+ t.belongs_to :dummy
39
+ t.timestamps
53
40
  end
54
41
 
55
- scope = @dummy.restrict('!')
56
- scope.should be_a_kind_of ActiveRecord::Relation
57
- scope.protector_subject.should == '!'
58
- end
42
+ Protector::Adapters::ActiveRecord.activate!
59
43
 
60
- it_behaves_like "a model"
61
- end
44
+ class Dummy < ActiveRecord::Base
45
+ protect do |x|
46
+ scope{ where('1=0') } if x == '-'
47
+ scope{ where(number: 999) } if x == '+'
48
+ end
62
49
 
63
- describe Protector::Adapters::ActiveRecord::Relation do
64
- before(:all) do
65
- @dummy = Class.new(ActiveRecord::Base) do
66
- self.table_name = "dummies"
50
+ scope :none, where('1 = 0') unless respond_to?(:none)
51
+ has_many :fluffies
67
52
  end
68
- end
53
+ Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
54
+ Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
69
55
 
70
- it "includes" do
71
- @dummy.all.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
72
- end
56
+ class Fluffy < ActiveRecord::Base
57
+ protect do |x|
58
+ scope{ where('1=0') } if x == '-'
59
+ scope{ where(number: 999) } if x == '+'
73
60
 
74
- it "saves subject" do
75
- @dummy.all.restrict('!').where(number: 999).protector_subject.should == '!'
61
+ can :view, :dummy_id unless x == '-'
62
+ end
63
+
64
+ scope :none, where('1 = 0') unless respond_to?(:none)
65
+ belongs_to :dummy
66
+ end
67
+ Fluffy.create! string: 'zomgstring', number: 999, dummy_id: 1
68
+ Fluffy.create! string: 'zomgstring', number: 777, dummy_id: 1
76
69
  end
77
70
 
78
- context "with null relation" do
71
+ describe Protector::Adapters::ActiveRecord::Base do
79
72
  before(:each) do
80
- @dummy.instance_eval do
81
- protect do; scope{ none }; end
73
+ @dummy = Class.new(ActiveRecord::Base) do
74
+ self.table_name = "dummies"
75
+ scope :none, where('1 = 0') unless respond_to?(:none)
82
76
  end
83
77
  end
84
78
 
85
- it "counts" do
86
- @dummy.all.count.should == 2
87
- @dummy.all.restrict('!').count.should == 0
79
+ it "includes" do
80
+ @dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
88
81
  end
89
82
 
90
- it "fetches" do
91
- fetched = @dummy.all.restrict('!').to_a
83
+ it "scopes" do
84
+ @dummy.instance_eval do
85
+ protect do; scope{ all }; end
86
+ end
87
+
88
+ scope = @dummy.restrict!('!')
89
+ scope.should be_a_kind_of ActiveRecord::Relation
90
+ scope.protector_subject.should == '!'
91
+ end
92
92
 
93
- @dummy.all.to_a.length.should == 2
94
- fetched.length.should == 0
93
+ it_behaves_like "a model"
94
+
95
+ 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
+ it "scopes" do
103
+ dummy = Dummy.restrict!('+').includes(:fluffies).first
104
+ dummy.fluffies.length.should == 1
105
+ end
95
106
  end
96
107
  end
97
108
 
98
- context "with active relation" do
99
- before(:each) do
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)
114
+ end
115
+ end
116
+
117
+ it "includes" do
118
+ @dummy.none.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
119
+ end
120
+
121
+ it "saves subject" do
122
+ @dummy.restrict!('!').where(number: 999).protector_subject.should == '!'
123
+ end
124
+
125
+ it "forwards subject" do
100
126
  @dummy.instance_eval do
101
- protect do; scope{ where(number: 999) }; end
127
+ protect do; end
102
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 == '!'
103
132
  end
104
133
 
105
- it "counts" do
106
- @dummy.all.count.should == 2
107
- @dummy.all.restrict('!').count.should == 1
134
+ 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
+ it "checks existence" do
142
+ @dummy.any?.should == true
143
+ @dummy.restrict!('!').any?.should == false
144
+ end
145
+
146
+ it "counts" do
147
+ @dummy.count.should == 2
148
+ @dummy.restrict!('!').count.should == 0
149
+ end
150
+
151
+ it "fetches" do
152
+ fetched = @dummy.restrict!('!').to_a
153
+
154
+ @dummy.all.length.should == 2
155
+ fetched.length.should == 0
156
+ end
157
+
158
+ it "keeps security scope when unscoped" do
159
+ @dummy.unscoped.restrict!('!').count.should == 0
160
+ @dummy.restrict!('!').unscoped.count.should == 0
161
+ end
108
162
  end
109
163
 
110
- it "fetches" do
111
- fetched = @dummy.all.restrict('!').to_a
164
+ 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
+ it "checks existence" do
172
+ @dummy.any?.should == true
173
+ @dummy.restrict!('!').any?.should == true
174
+ end
175
+
176
+ it "counts" do
177
+ @dummy.count.should == 2
178
+ @dummy.restrict!('!').count.should == 1
179
+ end
180
+
181
+ it "fetches" do
182
+ fetched = @dummy.restrict!('!').to_a
112
183
 
113
- @dummy.all.to_a.length.should == 2
114
- fetched.length.should == 1
184
+ @dummy.all.length.should == 2
185
+ fetched.length.should == 1
186
+ end
187
+
188
+ it "keeps security scope when unscoped" do
189
+ @dummy.unscoped.restrict!('!').count.should == 1
190
+ @dummy.restrict!('!').unscoped.count.should == 1
191
+ end
115
192
  end
116
193
  end
117
194
  end
195
+
118
196
  end
data/spec/lib/dsl_spec.rb CHANGED
@@ -7,21 +7,21 @@ describe Protector::DSL do
7
7
  end
8
8
 
9
9
  it "defines proper methods" do
10
- @base.instance_methods.should include(:restrict)
10
+ @base.instance_methods.should include(:restrict!)
11
11
  @base.instance_methods.should include(:protector_subject)
12
12
  end
13
13
 
14
14
  it "remembers protection subject" do
15
15
  base = @base.new
16
- base.restrict("universe")
16
+ base.restrict!("universe")
17
17
  base.protector_subject.should == "universe"
18
18
  end
19
19
 
20
20
  it "forgets protection subject" do
21
21
  base = @base.new
22
- base.restrict("universe")
22
+ base.restrict!("universe")
23
23
  base.protector_subject.should == "universe"
24
- base.unrestrict
24
+ base.unrestrict!
25
25
  base.protector_subject.should == nil
26
26
  end
27
27
  end
@@ -41,24 +41,23 @@ describe Protector::DSL do
41
41
  end
42
42
 
43
43
  describe Protector::DSL::Meta do
44
- l = -> (x) { x > 4 }
44
+ l = lambda {|x| x > 4 }
45
45
 
46
46
  before :each do
47
47
  @meta = Protector::DSL::Meta.new
48
48
 
49
- # << -> FTW!
50
- @meta << -> {
49
+ @meta << lambda {
51
50
  scope { 'relation' }
52
51
  }
53
52
 
54
- @meta << -> (user) {
53
+ @meta << lambda {|user|
55
54
  user.should == 'user'
56
55
 
57
56
  can :view
58
57
  cannot :view, %w(field5), :field4
59
58
  }
60
59
 
61
- @meta << -> (user, entry) {
60
+ @meta << lambda {|user, entry|
62
61
  user.should == 'user'
63
62
  entry.should == 'entry'
64
63
 
@@ -1,7 +1,6 @@
1
- require 'pry'
2
1
  require 'protector'
3
2
 
4
- require 'active_record'
3
+ Bundler.require
5
4
 
6
5
  require_relative 'model'
7
6