protector 0.0.2 → 0.0.4
Sign up to get free protection for your applications and to get access to all the features.
- 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
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA1:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: 5196d6460c7c9550f4be2cef9135916d900dcdc0
|
4
|
+
data.tar.gz: 08886a665e9f57db43dd49d064c23b569e01106b
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: 87cd3b93a9b8b3ca0b86d034234385111cd8aa9c793e6440908fc6362a6160e21a7d6010b6c89018a4245a44e1cb043c0086a023cf88ce50e501be7a21cece95
|
7
|
+
data.tar.gz: 2fffcf64bc2669bfb3292e432e52408d1a9207263085a6cac74dde4200882dc645523c93320449ffe9320f76aa94389caf3ce2c7c993ddfc56333e2426d1d5c8
|
data/.travis.yml
ADDED
data/Appraisals
ADDED
@@ -0,0 +1,9 @@
|
|
1
|
+
appraise "AR_3.2" do
|
2
|
+
gem "activerecord", "3.2", require: "active_record"
|
3
|
+
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby, github: "jruby/activerecord-jdbc-adapter"
|
4
|
+
end
|
5
|
+
|
6
|
+
appraise "AR_4" do
|
7
|
+
gem "activerecord", "4.0.0.rc1", require: "active_record"
|
8
|
+
gem "activerecord-jdbcsqlite3-adapter", platform: :jruby, github: "jruby/activerecord-jdbc-adapter"
|
9
|
+
end
|
data/Gemfile
CHANGED
data/README.md
CHANGED
@@ -1,6 +1,125 @@
|
|
1
1
|
# Protector
|
2
2
|
|
3
|
-
|
3
|
+
[![Build Status](https://travis-ci.org/inossidabile/protector.png?branch=master)](https://travis-ci.org/inossidabile/protector)
|
4
|
+
[![Code Climate](https://codeclimate.com/github/inossidabile/protector.png)](https://codeclimate.com/github/inossidabile/protector)
|
5
|
+
|
6
|
+
Protector is a Ruby ORM extension for managing security restrictions on a field level. The gem favors white-listing over black-listing (everything is disallowed by default), convention over configuration and is duck-type compatible with most of existing code.
|
7
|
+
|
8
|
+
Currently Protector supports the following ORM adapters:
|
9
|
+
|
10
|
+
* [ActiveRecord](http://guides.rubyonrails.org/active_record_querying.html) (>= 3.2)
|
11
|
+
|
12
|
+
We are working hard to extend the list with:
|
13
|
+
|
14
|
+
* [Sequel](http://sequel.rubyforge.org/)
|
15
|
+
* [DataMapper](http://datamapper.org/)
|
16
|
+
* [Mongoid](http://mongoid.org/en/mongoid/index.html)
|
17
|
+
|
18
|
+
## Basics
|
19
|
+
|
20
|
+
DSL of Protector is a Ruby block (or several) describing ACL separated into contexts (authorized user is a very typical example of a context). Each time the context of model changes, DSL blocks reevaluate internally to get an actual ACL that is then utilized internally to cut restricted actions.
|
21
|
+
|
22
|
+
Protector follows nondestructive blocking strategy. It returns `nil` when the forbidden field is requested and only checks creation (modification) capability during persisting. Even more: the latter is implemented as a model validation so it will seamlessly integrate into your typical workflow.
|
23
|
+
|
24
|
+
This example is based on ActiveRecord but the code is mostly identical for any supported adapter.
|
25
|
+
|
26
|
+
```ruby
|
27
|
+
class Article < ActiveRecord::Base # Fields: title, text, user_id, hidden
|
28
|
+
protect do |user| # `user` is a context of security
|
29
|
+
|
30
|
+
unless user.admin?
|
31
|
+
scope { where(hidden: false) } # Non-admins can only read insecure data
|
32
|
+
|
33
|
+
can :view # Allow to read any field
|
34
|
+
if user.nil? # User is unknown and therefore not authenticated
|
35
|
+
cannot :view, :text # Guests can't read the text
|
36
|
+
end
|
37
|
+
|
38
|
+
can :create, %w(title text) # Non-admins can't set `hidden` flag
|
39
|
+
can :create, user_id: lamda{|x| # ... and should correctly fill
|
40
|
+
x == user.id # ... the `user_id` association
|
41
|
+
}
|
42
|
+
|
43
|
+
# In this setup non-admins can not destroy or update existing records.
|
44
|
+
else
|
45
|
+
scope { all } # Admins can retrieve anything
|
46
|
+
|
47
|
+
can :view # ... and view anything
|
48
|
+
can :create # ... and create anything
|
49
|
+
can :update # ... and update anything
|
50
|
+
can :destroy # ... and they can delete
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
54
|
+
```
|
55
|
+
|
56
|
+
Now that we have ACL described we can enable it as easy as:
|
57
|
+
|
58
|
+
```ruby
|
59
|
+
article.restrict!(current_user) # Assuming article is an instance of Article
|
60
|
+
```
|
61
|
+
|
62
|
+
To make model unsafe again call:
|
63
|
+
|
64
|
+
```ruby
|
65
|
+
article.unrestrict!
|
66
|
+
```
|
67
|
+
|
68
|
+
**Both methods are chainable!**
|
69
|
+
|
70
|
+
## Scopes
|
71
|
+
|
72
|
+
Besides the `can` and `cannot` directives Protector also handles relations visibility. In the previous sample the following block is responsible to make hidden articles actually hide:
|
73
|
+
|
74
|
+
```ruby
|
75
|
+
scope { where(hidden: false) } # Non-admins can only read unsecure data
|
76
|
+
````
|
77
|
+
|
78
|
+
Make sure to write the block content of the `scope` directive in the notation of your ORM library.
|
79
|
+
|
80
|
+
To finally utilize this function use the same `restrict!` method on a level of Class or Relation. Like this:
|
81
|
+
|
82
|
+
```ruby
|
83
|
+
Article.restrict!(current_user).where(...)
|
84
|
+
# OR
|
85
|
+
Article.where(...).restrict!(current_user)
|
86
|
+
```
|
87
|
+
|
88
|
+
Note that you don't need to explicitly restrict models you get from a restricted scope – they born restricted.
|
89
|
+
|
90
|
+
## Self-aware conditions
|
91
|
+
|
92
|
+
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.
|
93
|
+
|
94
|
+
The following example extends Article to allow users edit their own posts:
|
95
|
+
|
96
|
+
```ruby
|
97
|
+
class Article < ActiveRecord::Base # Fields: title, text, user_id, hidden
|
98
|
+
protect do |user, article|
|
99
|
+
if user
|
100
|
+
if article.try(:user_id) == user.id # Checks belonging keeping possible nil in mind
|
101
|
+
can :update, %w(title text) # Allow authors to modify posts
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
end
|
106
|
+
```
|
107
|
+
|
108
|
+
## Associations
|
109
|
+
|
110
|
+
Protector is aware of associations. All the associations retrieved from restricted instance will automatically be restricted to the same context. Therefore you don't have to do anything special – it will respect proper scopes out of the box.
|
111
|
+
|
112
|
+
The access to `belongs_to` kind of association depends on corresponding foreign key readability.
|
113
|
+
|
114
|
+
## Ideology
|
115
|
+
|
116
|
+
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:
|
117
|
+
|
118
|
+
* it works inside of the model instead of wrapping it into a proxy: that's why it's compatible with every other extension you use
|
119
|
+
* it secures persistence and not object properties: you can modify any properties you want but it's not going to let you save what you can not save
|
120
|
+
* it respects the differentiation between business-logic layer and SQL layer: protection is validation so any method that skips validation will also avoid the security check
|
121
|
+
|
122
|
+
**The last thing is really important to understand. No matter if you can read a field or not, methods like `.pluck` are still capable of reading any of your fields and if you tell your model to skip validation it will also skip an ACL check.**
|
4
123
|
|
5
124
|
## Installation
|
6
125
|
|
@@ -16,9 +135,13 @@ Or install it yourself as:
|
|
16
135
|
|
17
136
|
$ gem install protector
|
18
137
|
|
19
|
-
|
138
|
+
As long as you load Protector after an ORM library it is supposed to activate itself automatically. Otherwise you can enable required adapter manually:
|
139
|
+
|
140
|
+
```ruby
|
141
|
+
Protector::Adapters::ActiveRecord.activate!
|
142
|
+
```
|
20
143
|
|
21
|
-
|
144
|
+
Where "ActiveRecord" is the adapter you are about to use. It can be "Sequel", "DataMapper", "Mongoid".
|
22
145
|
|
23
146
|
## Contributing
|
24
147
|
|
data/Rakefile
CHANGED
@@ -1,8 +1,13 @@
|
|
1
1
|
require 'bundler/setup'
|
2
2
|
require 'bundler/gem_tasks'
|
3
3
|
require 'rspec/core/rake_task'
|
4
|
+
require 'appraisal'
|
4
5
|
|
5
6
|
RSpec::Core::RakeTask.new(:spec)
|
6
7
|
|
7
|
-
|
8
|
-
|
8
|
+
task :default => :all
|
9
|
+
|
10
|
+
desc 'Test the plugin under all supported Rails versions.'
|
11
|
+
task :all => ["appraisal:install"] do |t|
|
12
|
+
exec('rake appraisal spec')
|
13
|
+
end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rake"
|
6
|
+
gem "pry"
|
7
|
+
gem "rspec"
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "appraisal"
|
11
|
+
gem "sqlite3", :platform=>:ruby
|
12
|
+
gem "jdbc-sqlite3", :platform=>:jruby
|
13
|
+
gem "activerecord", "3.2", :require=>"active_record"
|
14
|
+
gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby, :github=>"jruby/activerecord-jdbc-adapter"
|
15
|
+
|
16
|
+
gemspec :path=>"../"
|
@@ -0,0 +1,104 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/jruby/activerecord-jdbc-adapter.git
|
3
|
+
revision: b1cb2cb59496a7c3ae22799f88c8c4789e6a8cce
|
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
|
+
|
9
|
+
PATH
|
10
|
+
remote: /Users/inossidabile/Repos/protector
|
11
|
+
specs:
|
12
|
+
protector (0.0.3)
|
13
|
+
activesupport
|
14
|
+
i18n
|
15
|
+
|
16
|
+
GEM
|
17
|
+
remote: https://rubygems.org/
|
18
|
+
specs:
|
19
|
+
activemodel (3.2.0)
|
20
|
+
activesupport (= 3.2.0)
|
21
|
+
builder (~> 3.0.0)
|
22
|
+
activerecord (3.2.0)
|
23
|
+
activemodel (= 3.2.0)
|
24
|
+
activesupport (= 3.2.0)
|
25
|
+
arel (~> 3.0.0)
|
26
|
+
tzinfo (~> 0.3.29)
|
27
|
+
activerecord-jdbc-adapter (1.3.0.beta1)
|
28
|
+
activesupport (3.2.0)
|
29
|
+
i18n (~> 0.6)
|
30
|
+
multi_json (~> 1.0)
|
31
|
+
appraisal (0.5.2)
|
32
|
+
bundler
|
33
|
+
rake
|
34
|
+
arel (3.0.2)
|
35
|
+
builder (3.0.4)
|
36
|
+
coderay (1.0.9)
|
37
|
+
diff-lcs (1.2.4)
|
38
|
+
ffi (1.8.1)
|
39
|
+
ffi (1.8.1-java)
|
40
|
+
formatador (0.2.4)
|
41
|
+
guard (1.8.0)
|
42
|
+
formatador (>= 0.2.4)
|
43
|
+
listen (>= 1.0.0)
|
44
|
+
lumberjack (>= 1.0.2)
|
45
|
+
pry (>= 0.9.10)
|
46
|
+
thor (>= 0.14.6)
|
47
|
+
guard-rspec (3.0.0)
|
48
|
+
guard (>= 1.8)
|
49
|
+
rspec (~> 2.13)
|
50
|
+
i18n (0.6.4)
|
51
|
+
jdbc-sqlite3 (3.7.2)
|
52
|
+
listen (1.1.3)
|
53
|
+
rb-fsevent (>= 0.9.3)
|
54
|
+
rb-inotify (>= 0.9)
|
55
|
+
rb-kqueue (>= 0.2)
|
56
|
+
lumberjack (1.0.3)
|
57
|
+
method_source (0.8.1)
|
58
|
+
multi_json (1.7.3)
|
59
|
+
pry (0.9.12.2)
|
60
|
+
coderay (~> 1.0.5)
|
61
|
+
method_source (~> 0.8)
|
62
|
+
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
|
+
rake (10.0.4)
|
69
|
+
rb-fsevent (0.9.3)
|
70
|
+
rb-inotify (0.9.0)
|
71
|
+
ffi (>= 0.5.0)
|
72
|
+
rb-kqueue (0.2.0)
|
73
|
+
ffi (>= 0.5.0)
|
74
|
+
rspec (2.13.0)
|
75
|
+
rspec-core (~> 2.13.0)
|
76
|
+
rspec-expectations (~> 2.13.0)
|
77
|
+
rspec-mocks (~> 2.13.0)
|
78
|
+
rspec-core (2.13.1)
|
79
|
+
rspec-expectations (2.13.0)
|
80
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
81
|
+
rspec-mocks (2.13.1)
|
82
|
+
slop (3.4.5)
|
83
|
+
spoon (0.0.4)
|
84
|
+
ffi
|
85
|
+
sqlite3 (1.3.7)
|
86
|
+
thor (0.18.1)
|
87
|
+
tzinfo (0.3.37)
|
88
|
+
|
89
|
+
PLATFORMS
|
90
|
+
java
|
91
|
+
ruby
|
92
|
+
|
93
|
+
DEPENDENCIES
|
94
|
+
activerecord (= 3.2)
|
95
|
+
activerecord-jdbcsqlite3-adapter!
|
96
|
+
appraisal
|
97
|
+
guard
|
98
|
+
guard-rspec
|
99
|
+
jdbc-sqlite3
|
100
|
+
protector!
|
101
|
+
pry
|
102
|
+
rake
|
103
|
+
rspec
|
104
|
+
sqlite3
|
@@ -0,0 +1,16 @@
|
|
1
|
+
# This file was generated by Appraisal
|
2
|
+
|
3
|
+
source "https://rubygems.org"
|
4
|
+
|
5
|
+
gem "rake"
|
6
|
+
gem "pry"
|
7
|
+
gem "rspec"
|
8
|
+
gem "guard"
|
9
|
+
gem "guard-rspec"
|
10
|
+
gem "appraisal"
|
11
|
+
gem "sqlite3", :platform=>:ruby
|
12
|
+
gem "jdbc-sqlite3", :platform=>:jruby
|
13
|
+
gem "activerecord", "4.0.0.rc1", :require=>"active_record"
|
14
|
+
gem "activerecord-jdbcsqlite3-adapter", :platform=>:jruby, :github=>"jruby/activerecord-jdbc-adapter"
|
15
|
+
|
16
|
+
gemspec :path=>"../"
|
@@ -0,0 +1,113 @@
|
|
1
|
+
GIT
|
2
|
+
remote: git://github.com/jruby/activerecord-jdbc-adapter.git
|
3
|
+
revision: b1cb2cb59496a7c3ae22799f88c8c4789e6a8cce
|
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
|
+
|
9
|
+
PATH
|
10
|
+
remote: /Users/inossidabile/Repos/protector
|
11
|
+
specs:
|
12
|
+
protector (0.0.3)
|
13
|
+
activesupport
|
14
|
+
i18n
|
15
|
+
|
16
|
+
GEM
|
17
|
+
remote: https://rubygems.org/
|
18
|
+
specs:
|
19
|
+
activemodel (4.0.0.rc1)
|
20
|
+
activesupport (= 4.0.0.rc1)
|
21
|
+
builder (~> 3.1.0)
|
22
|
+
activerecord (4.0.0.rc1)
|
23
|
+
activemodel (= 4.0.0.rc1)
|
24
|
+
activerecord-deprecated_finders (~> 1.0.2)
|
25
|
+
activesupport (= 4.0.0.rc1)
|
26
|
+
arel (~> 4.0.0)
|
27
|
+
activerecord-deprecated_finders (1.0.2)
|
28
|
+
activerecord-jdbc-adapter (1.3.0.beta1)
|
29
|
+
activesupport (4.0.0.rc1)
|
30
|
+
i18n (~> 0.6, >= 0.6.4)
|
31
|
+
minitest (~> 4.2)
|
32
|
+
multi_json (~> 1.3)
|
33
|
+
thread_safe (~> 0.1)
|
34
|
+
tzinfo (~> 0.3.37)
|
35
|
+
appraisal (0.5.2)
|
36
|
+
bundler
|
37
|
+
rake
|
38
|
+
arel (4.0.0)
|
39
|
+
atomic (1.1.9)
|
40
|
+
atomic (1.1.9-java)
|
41
|
+
builder (3.1.4)
|
42
|
+
coderay (1.0.9)
|
43
|
+
diff-lcs (1.2.4)
|
44
|
+
ffi (1.8.1)
|
45
|
+
ffi (1.8.1-java)
|
46
|
+
formatador (0.2.4)
|
47
|
+
guard (1.8.0)
|
48
|
+
formatador (>= 0.2.4)
|
49
|
+
listen (>= 1.0.0)
|
50
|
+
lumberjack (>= 1.0.2)
|
51
|
+
pry (>= 0.9.10)
|
52
|
+
thor (>= 0.14.6)
|
53
|
+
guard-rspec (3.0.0)
|
54
|
+
guard (>= 1.8)
|
55
|
+
rspec (~> 2.13)
|
56
|
+
i18n (0.6.4)
|
57
|
+
jdbc-sqlite3 (3.7.2)
|
58
|
+
listen (1.1.3)
|
59
|
+
rb-fsevent (>= 0.9.3)
|
60
|
+
rb-inotify (>= 0.9)
|
61
|
+
rb-kqueue (>= 0.2)
|
62
|
+
lumberjack (1.0.3)
|
63
|
+
method_source (0.8.1)
|
64
|
+
minitest (4.7.4)
|
65
|
+
multi_json (1.7.3)
|
66
|
+
pry (0.9.12.2)
|
67
|
+
coderay (~> 1.0.5)
|
68
|
+
method_source (~> 0.8)
|
69
|
+
slop (~> 3.4)
|
70
|
+
pry (0.9.12.2-java)
|
71
|
+
coderay (~> 1.0.5)
|
72
|
+
method_source (~> 0.8)
|
73
|
+
slop (~> 3.4)
|
74
|
+
spoon (~> 0.0)
|
75
|
+
rake (10.0.4)
|
76
|
+
rb-fsevent (0.9.3)
|
77
|
+
rb-inotify (0.9.0)
|
78
|
+
ffi (>= 0.5.0)
|
79
|
+
rb-kqueue (0.2.0)
|
80
|
+
ffi (>= 0.5.0)
|
81
|
+
rspec (2.13.0)
|
82
|
+
rspec-core (~> 2.13.0)
|
83
|
+
rspec-expectations (~> 2.13.0)
|
84
|
+
rspec-mocks (~> 2.13.0)
|
85
|
+
rspec-core (2.13.1)
|
86
|
+
rspec-expectations (2.13.0)
|
87
|
+
diff-lcs (>= 1.1.3, < 2.0)
|
88
|
+
rspec-mocks (2.13.1)
|
89
|
+
slop (3.4.5)
|
90
|
+
spoon (0.0.4)
|
91
|
+
ffi
|
92
|
+
sqlite3 (1.3.7)
|
93
|
+
thor (0.18.1)
|
94
|
+
thread_safe (0.1.0)
|
95
|
+
atomic
|
96
|
+
tzinfo (0.3.37)
|
97
|
+
|
98
|
+
PLATFORMS
|
99
|
+
java
|
100
|
+
ruby
|
101
|
+
|
102
|
+
DEPENDENCIES
|
103
|
+
activerecord (= 4.0.0.rc1)
|
104
|
+
activerecord-jdbcsqlite3-adapter!
|
105
|
+
appraisal
|
106
|
+
guard
|
107
|
+
guard-rspec
|
108
|
+
jdbc-sqlite3
|
109
|
+
protector!
|
110
|
+
pry
|
111
|
+
rake
|
112
|
+
rspec
|
113
|
+
sqlite3
|
@@ -0,0 +1,29 @@
|
|
1
|
+
module Protector
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Association
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
alias_method_chain :scope, :protector
|
9
|
+
end
|
10
|
+
|
11
|
+
def scope_with_protector(*args)
|
12
|
+
scope_without_protector(*args).restrict!(owner.protector_subject)
|
13
|
+
end
|
14
|
+
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
|
+
end
|
28
|
+
end
|
29
|
+
end
|
@@ -0,0 +1,100 @@
|
|
1
|
+
module Protector
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Base
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include Protector::DSL::Base
|
9
|
+
include Protector::DSL::Entry
|
10
|
+
|
11
|
+
validate(on: :create) do
|
12
|
+
return unless @protector_subject
|
13
|
+
errors[:base] << I18n.t('protector.invalid') unless creatable?
|
14
|
+
end
|
15
|
+
|
16
|
+
validate(on: :update) do
|
17
|
+
return unless @protector_subject
|
18
|
+
errors[:base] << I18n.t('protector.invalid') unless updatable?
|
19
|
+
end
|
20
|
+
|
21
|
+
before_destroy do
|
22
|
+
return true unless @protector_subject
|
23
|
+
destroyable?
|
24
|
+
end
|
25
|
+
|
26
|
+
if Gem::Version.new(::ActiveRecord::VERSION::STRING) < Gem::Version.new('4.0.0.rc1')
|
27
|
+
def self.restrict!(subject)
|
28
|
+
scoped.restrict!(subject)
|
29
|
+
end
|
30
|
+
else
|
31
|
+
def self.restrict!(subject)
|
32
|
+
all.restrict!(subject)
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def [](name)
|
37
|
+
if !@protector_subject || name == self.class.primary_key || protector_meta.readable?(name)
|
38
|
+
read_attribute(name)
|
39
|
+
else
|
40
|
+
nil
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
module ClassMethods
|
46
|
+
def define_method_attribute(name)
|
47
|
+
super
|
48
|
+
|
49
|
+
unless (primary_key == name || (primary_key.is_a?(Array) && primary_key.include?(name)))
|
50
|
+
generated_attribute_methods.module_eval <<-STR, __FILE__, __LINE__ + 1
|
51
|
+
alias_method #{"#{name}_unprotected".inspect}, #{name.inspect}
|
52
|
+
|
53
|
+
def #{name}
|
54
|
+
if !@protector_subject || protector_meta.readable?(#{name.inspect})
|
55
|
+
#{name}_unprotected
|
56
|
+
else
|
57
|
+
nil
|
58
|
+
end
|
59
|
+
end
|
60
|
+
STR
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
|
65
|
+
def protector_meta
|
66
|
+
unless @protector_subject
|
67
|
+
raise "Unprotected entity detected: use `restrict` method to protect it."
|
68
|
+
end
|
69
|
+
|
70
|
+
self.class.protector_meta.evaluate(
|
71
|
+
self.class,
|
72
|
+
self.class.column_names,
|
73
|
+
@protector_subject,
|
74
|
+
self
|
75
|
+
)
|
76
|
+
end
|
77
|
+
|
78
|
+
def visible?
|
79
|
+
protector_meta.relation.where(
|
80
|
+
self.class.primary_key => id
|
81
|
+
).any?
|
82
|
+
end
|
83
|
+
|
84
|
+
def creatable?
|
85
|
+
fields = HashWithIndifferentAccess[changed.map{|x| [x, read_attribute(x)]}]
|
86
|
+
protector_meta.creatable?(fields)
|
87
|
+
end
|
88
|
+
|
89
|
+
def updatable?
|
90
|
+
fields = HashWithIndifferentAccess[changed.map{|x| [x, read_attribute(x)]}]
|
91
|
+
protector_meta.updatable?(fields)
|
92
|
+
end
|
93
|
+
|
94
|
+
def destroyable?
|
95
|
+
protector_meta.destroyable?
|
96
|
+
end
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
@@ -0,0 +1,49 @@
|
|
1
|
+
module Protector
|
2
|
+
module Adapters
|
3
|
+
module ActiveRecord
|
4
|
+
module Relation
|
5
|
+
extend ActiveSupport::Concern
|
6
|
+
|
7
|
+
included do
|
8
|
+
include Protector::DSL::Base
|
9
|
+
|
10
|
+
alias_method_chain :exec_queries, :protector
|
11
|
+
end
|
12
|
+
|
13
|
+
def protector_meta
|
14
|
+
@klass.protector_meta.evaluate(@klass, @klass.column_names, @protector_subject)
|
15
|
+
end
|
16
|
+
|
17
|
+
def unscoped
|
18
|
+
super.restrict!(@protector_subject)
|
19
|
+
end
|
20
|
+
|
21
|
+
def count(*args)
|
22
|
+
super || 0
|
23
|
+
end
|
24
|
+
|
25
|
+
def sum(*args)
|
26
|
+
super || 0
|
27
|
+
end
|
28
|
+
|
29
|
+
def calculate(*args)
|
30
|
+
return super unless @protector_subject
|
31
|
+
merge(protector_meta.relation).unrestrict!.calculate *args
|
32
|
+
end
|
33
|
+
|
34
|
+
def exists?(*args)
|
35
|
+
return super unless @protector_subject
|
36
|
+
merge(protector_meta.relation).unrestrict!.exists? *args
|
37
|
+
end
|
38
|
+
|
39
|
+
def exec_queries_with_protector(*args)
|
40
|
+
return exec_queries_without_protector unless @protector_subject
|
41
|
+
|
42
|
+
subject = @protector_subject
|
43
|
+
relation = merge(protector_meta.relation).unrestrict!
|
44
|
+
@records = relation.send(:exec_queries).each{|r| r.restrict!(subject)}
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|