protector 0.0.2 → 0.0.4
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.
- checksums.yaml +4 -4
- data/.travis.yml +6 -0
- data/Appraisals +9 -0
- data/Gemfile +4 -1
- data/README.md +126 -3
- data/Rakefile +7 -2
- data/gemfiles/AR_3.2.gemfile +16 -0
- data/gemfiles/AR_3.2.gemfile.lock +104 -0
- data/gemfiles/AR_4.gemfile +16 -0
- data/gemfiles/AR_4.gemfile.lock +113 -0
- data/lib/protector/adapters/active_record/association.rb +29 -0
- data/lib/protector/adapters/active_record/base.rb +100 -0
- data/lib/protector/adapters/active_record/relation.rb +49 -0
- data/lib/protector/adapters/active_record.rb +5 -145
- data/lib/protector/dsl.rb +8 -4
- data/lib/protector/version.rb +1 -1
- data/lib/protector.rb +2 -5
- data/spec/lib/adapters/active_record_spec.rb +156 -78
- data/spec/lib/dsl_spec.rb +8 -9
- data/spec/spec_helpers/boot.rb +1 -2
- data/spec/spec_helpers/model.rb +66 -72
- metadata +11 -2
@@ -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
|
-
|
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
|
-
|
20
|
+
case b.arity
|
21
|
+
when 2
|
18
22
|
instance_exec subject, entry, &b
|
19
|
-
|
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
|
data/lib/protector/version.rb
CHANGED
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
|
-
|
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
|
-
|
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
|
-
|
24
|
-
|
25
|
-
|
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
|
-
|
31
|
-
|
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
|
40
|
-
before(:
|
41
|
-
|
42
|
-
|
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
|
-
|
51
|
-
|
52
|
-
|
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
|
-
|
56
|
-
scope.should be_a_kind_of ActiveRecord::Relation
|
57
|
-
scope.protector_subject.should == '!'
|
58
|
-
end
|
42
|
+
Protector::Adapters::ActiveRecord.activate!
|
59
43
|
|
60
|
-
|
61
|
-
|
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
|
-
|
64
|
-
|
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
|
-
|
53
|
+
Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
|
54
|
+
Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
|
69
55
|
|
70
|
-
|
71
|
-
|
72
|
-
|
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
|
-
|
75
|
-
|
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
|
-
|
71
|
+
describe Protector::Adapters::ActiveRecord::Base do
|
79
72
|
before(:each) do
|
80
|
-
@dummy.
|
81
|
-
|
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 "
|
86
|
-
@dummy.
|
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 "
|
91
|
-
|
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
|
-
|
94
|
-
|
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
|
-
|
99
|
-
before(:
|
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;
|
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
|
-
|
106
|
-
|
107
|
-
|
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
|
-
|
111
|
-
|
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
|
-
|
114
|
-
|
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 =
|
44
|
+
l = lambda {|x| x > 4 }
|
45
45
|
|
46
46
|
before :each do
|
47
47
|
@meta = Protector::DSL::Meta.new
|
48
48
|
|
49
|
-
|
50
|
-
@meta << -> {
|
49
|
+
@meta << lambda {
|
51
50
|
scope { 'relation' }
|
52
51
|
}
|
53
52
|
|
54
|
-
@meta <<
|
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 <<
|
60
|
+
@meta << lambda {|user, entry|
|
62
61
|
user.should == 'user'
|
63
62
|
entry.should == 'entry'
|
64
63
|
|