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
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
|
+
[](https://travis-ci.org/inossidabile/protector)
|
4
|
+
[](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
|