heimdallr 0.0.1 → 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +2 -0
- data/.yardopts +1 -0
- data/LICENSE +19 -0
- data/README.md +128 -0
- data/README.yard.md +128 -0
- data/Rakefile +20 -0
- data/heimdallr.gemspec +2 -3
- data/lib/heimdallr.rb +39 -2
- data/lib/heimdallr/evaluator.rb +139 -41
- data/lib/heimdallr/model.rb +49 -18
- data/lib/heimdallr/proxy/collection.rb +28 -0
- data/lib/heimdallr/proxy/record.rb +236 -0
- data/lib/heimdallr/resource.rb +220 -80
- data/lib/heimdallr/validator.rb +15 -0
- metadata +19 -13
- data/lib/heimdallr/proxy.rb +0 -61
- data/lib/heimdallr/version.rb +0 -3
data/.gitignore
CHANGED
data/.yardopts
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--no-private --protected -r README.yard.md --exclude README.md - LICENSE
|
data/LICENSE
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (C) 2012 Peter Zotov <whitequark@whitequark.org>
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
4
|
+
this software and associated documentation files (the "Software"), to deal in
|
5
|
+
the Software without restriction, including without limitation the rights to
|
6
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
7
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
8
|
+
so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in all
|
11
|
+
copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
19
|
+
SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
Heimdallr
|
2
|
+
=========
|
3
|
+
|
4
|
+
Heimdallr is a gem for managing security restrictions for ActiveRecord objects on field level; think
|
5
|
+
of it as a supercharged [CanCan](https://github.com/ryanb/cancan). Heimdallr favors whitelisting over blacklisting,
|
6
|
+
convention over configuration and is duck-type compatible with most of existing code.
|
7
|
+
|
8
|
+
``` ruby
|
9
|
+
# Define a typical set of models.
|
10
|
+
class User < ActiveRecord::Base
|
11
|
+
has_many :articles
|
12
|
+
end
|
13
|
+
|
14
|
+
class Article < ActiveRecord::Base
|
15
|
+
include Heimdallr::Model
|
16
|
+
|
17
|
+
belongs_to :owner, :class_name => 'User'
|
18
|
+
|
19
|
+
restrict do |user|
|
20
|
+
if user.admin? || user == self.owner
|
21
|
+
# Administrator or owner can do everything
|
22
|
+
can :fetch
|
23
|
+
can [:view, :create, :update, :destroy]
|
24
|
+
else
|
25
|
+
# Other users can view only non-classified articles...
|
26
|
+
can :fetch, -> { where('secrecy_level < ?', 5) }
|
27
|
+
|
28
|
+
# ... and see all fields except the actual security level...
|
29
|
+
can :view
|
30
|
+
cannot :view, [:secrecy_level]
|
31
|
+
|
32
|
+
# ... and can create them with certain restrictions.
|
33
|
+
can [:create, :update], {
|
34
|
+
owner: user,
|
35
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create some fictional data.
|
42
|
+
admin = User.create admin: true
|
43
|
+
johndoe = User.create admin: false
|
44
|
+
|
45
|
+
Article.create id: 1, owner: admin, content: "Nothing happens", secrecy_level: 0
|
46
|
+
Article.create id: 2, owner: admin, content: "This is a secret", secrecy_level: 10
|
47
|
+
Article.create id: 3, owner: johndoe, content: "Hello World"
|
48
|
+
|
49
|
+
# Get a restricted scope for the user.
|
50
|
+
secure = Article.restrict(johndoe)
|
51
|
+
|
52
|
+
# Use any ARel methods:
|
53
|
+
secure.pluck(:content)
|
54
|
+
# => ["Nothing happens", "Hello World"]
|
55
|
+
secure.find(1).secrecy_level
|
56
|
+
# => nil
|
57
|
+
|
58
|
+
# Everything should be permitted explicitly:
|
59
|
+
secure.first.delete
|
60
|
+
# ! Heimdallr::PermissionError is raised
|
61
|
+
|
62
|
+
# If only a single value is possible, it is inferred automatically:
|
63
|
+
secure.create! content: "My second article"
|
64
|
+
# => Article(id: 4, owner: johndoe, content: "My second article", security_level: 0)
|
65
|
+
|
66
|
+
# ... and cannot be changed:
|
67
|
+
secure.create! owner: admin, content: "I'm a haxx0r"
|
68
|
+
# ! ActiveRecord::RecordInvalid is raised
|
69
|
+
|
70
|
+
# You can use any valid ActiveRecord validators, too:
|
71
|
+
secure.create! content: "Top Secret", secrecy_level: 10
|
72
|
+
# ! ActiveRecord::RecordInvalid is raised
|
73
|
+
|
74
|
+
# John Doe would not see what he is not permitted to, ever:
|
75
|
+
# -- I know that you have this classified material! It's in folder #2.
|
76
|
+
secure.find 2
|
77
|
+
# ! ActiveRecord::RecordNotFound is raised
|
78
|
+
# -- No, it is not.
|
79
|
+
```
|
80
|
+
|
81
|
+
The DSL is described in documentation for [Heimdallr::Model](http://rubydoc.info/gems/heimdallr/0.0.2/Heimdallr/Model).
|
82
|
+
|
83
|
+
Note that Heimdallr is designed with three goals in mind, in the following order:
|
84
|
+
|
85
|
+
* Preventing malicious modifications
|
86
|
+
* Preventing information leaks
|
87
|
+
* Being convenient to use
|
88
|
+
|
89
|
+
Due to the last one, not all methods will raise an exception on invalid access; some will silently drop the offending
|
90
|
+
attribute or simply return `nil`. This is clearly described in the documentation, done intentionally and isn't
|
91
|
+
going to change.
|
92
|
+
|
93
|
+
REST interface
|
94
|
+
--------------
|
95
|
+
|
96
|
+
Heimdallr also favors REST pattern; while its use is not mandated, a Heimdallr::Resource module is provided, which
|
97
|
+
implements all standard REST actions with the extension of allowing to pass multiple models at once, and also enables
|
98
|
+
one to introspect all writable fields with `new` and `edit` actions.
|
99
|
+
|
100
|
+
The interface is described in documentation for [Heimdallr::Resource](http://rubydoc.info/gems/heimdallr/0.0.2/Heimdallr/Resource).
|
101
|
+
|
102
|
+
Compatibility
|
103
|
+
-------------
|
104
|
+
|
105
|
+
Ruby 1.8 and ActiveRecord versions prior to 3.0 are not supported.
|
106
|
+
|
107
|
+
Licensing
|
108
|
+
---------
|
109
|
+
|
110
|
+
Copyright (C) 2012 Peter Zotov <whitequark@whitequark.org>
|
111
|
+
|
112
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
113
|
+
this software and associated documentation files (the "Software"), to deal in
|
114
|
+
the Software without restriction, including without limitation the rights to
|
115
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
116
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
117
|
+
so, subject to the following conditions:
|
118
|
+
|
119
|
+
The above copyright notice and this permission notice shall be included in all
|
120
|
+
copies or substantial portions of the Software.
|
121
|
+
|
122
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
123
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
124
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
125
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
126
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
127
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
128
|
+
SOFTWARE.
|
data/README.yard.md
ADDED
@@ -0,0 +1,128 @@
|
|
1
|
+
Heimdallr
|
2
|
+
=========
|
3
|
+
|
4
|
+
Heimdallr is a gem for managing security restrictions for ActiveRecord objects on field level; think
|
5
|
+
of it as a supercharged [CanCan](https://github.com/ryanb/cancan). Heimdallr favors whitelisting over blacklisting,
|
6
|
+
convention over configuration and is duck-type compatible with most of existing code.
|
7
|
+
|
8
|
+
``` ruby
|
9
|
+
# Define a typical set of models.
|
10
|
+
class User < ActiveRecord::Base
|
11
|
+
has_many :articles
|
12
|
+
end
|
13
|
+
|
14
|
+
class Article < ActiveRecord::Base
|
15
|
+
include Heimdallr::Model
|
16
|
+
|
17
|
+
belongs_to :owner, :class_name => 'User'
|
18
|
+
|
19
|
+
restrict do |user|
|
20
|
+
if user.admin? || user == self.owner
|
21
|
+
# Administrator or owner can do everything
|
22
|
+
can :fetch
|
23
|
+
can [:view, :create, :update, :destroy]
|
24
|
+
else
|
25
|
+
# Other users can view only non-classified articles...
|
26
|
+
can :fetch, -> { where('secrecy_level < ?', 5) }
|
27
|
+
|
28
|
+
# ... and see all fields except the actual security level...
|
29
|
+
can :view
|
30
|
+
cannot :view, [:secrecy_level]
|
31
|
+
|
32
|
+
# ... and can create them with certain restrictions.
|
33
|
+
can [:create, :update], {
|
34
|
+
owner: user,
|
35
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
36
|
+
}
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
# Create some fictional data.
|
42
|
+
admin = User.create admin: true
|
43
|
+
johndoe = User.create admin: false
|
44
|
+
|
45
|
+
Article.create id: 1, owner: admin, content: "Nothing happens", secrecy_level: 0
|
46
|
+
Article.create id: 2, owner: admin, content: "This is a secret", secrecy_level: 10
|
47
|
+
Article.create id: 3, owner: johndoe, content: "Hello World"
|
48
|
+
|
49
|
+
# Get a restricted scope for the user.
|
50
|
+
secure = Article.restrict(johndoe)
|
51
|
+
|
52
|
+
# Use any ARel methods:
|
53
|
+
secure.pluck(:content)
|
54
|
+
# => ["Nothing happens", "Hello World"]
|
55
|
+
secure.find(1).secrecy_level
|
56
|
+
# => nil
|
57
|
+
|
58
|
+
# Everything should be permitted explicitly:
|
59
|
+
secure.first.delete
|
60
|
+
# ! Heimdallr::PermissionError is raised
|
61
|
+
|
62
|
+
# If only a single value is possible, it is inferred automatically:
|
63
|
+
secure.create! content: "My second article"
|
64
|
+
# => Article(id: 4, owner: johndoe, content: "My second article", security_level: 0)
|
65
|
+
|
66
|
+
# ... and cannot be changed:
|
67
|
+
secure.create! owner: admin, content: "I'm a haxx0r"
|
68
|
+
# ! ActiveRecord::RecordInvalid is raised
|
69
|
+
|
70
|
+
# You can use any valid ActiveRecord validators, too:
|
71
|
+
secure.create! content: "Top Secret", secrecy_level: 10
|
72
|
+
# ! ActiveRecord::RecordInvalid is raised
|
73
|
+
|
74
|
+
# John Doe would not see what he is not permitted to, ever:
|
75
|
+
# -- I know that you have this classified material! It's in folder #2.
|
76
|
+
secure.find 2
|
77
|
+
# ! ActiveRecord::RecordNotFound is raised
|
78
|
+
# -- No, it is not.
|
79
|
+
```
|
80
|
+
|
81
|
+
The DSL is described in documentation for {Heimdallr::Model}.
|
82
|
+
|
83
|
+
Note that Heimdallr is designed with three goals in mind, in the following order:
|
84
|
+
|
85
|
+
* Preventing malicious modifications
|
86
|
+
* Preventing information leaks
|
87
|
+
* Being convenient to use
|
88
|
+
|
89
|
+
Due to the last one, not all methods will raise an exception on invalid access; some will silently drop the offending
|
90
|
+
attribute or simply return `nil`. This is clearly described in the documentation, done intentionally and isn't
|
91
|
+
going to change.
|
92
|
+
|
93
|
+
REST interface
|
94
|
+
--------------
|
95
|
+
|
96
|
+
Heimdallr also favors REST pattern; while its use is not mandated, a Heimdallr::Resource module is provided, which
|
97
|
+
implements all standard REST actions with the extension of allowing to pass multiple models at once, and also enables
|
98
|
+
one to introspect all writable fields with `new` and `edit` actions.
|
99
|
+
|
100
|
+
The interface is described in documentation for {Heimdallr::Resource}.
|
101
|
+
|
102
|
+
Compatibility
|
103
|
+
-------------
|
104
|
+
|
105
|
+
Ruby 1.8 and ActiveRecord versions prior to 3.0 are not supported.
|
106
|
+
|
107
|
+
Licensing
|
108
|
+
---------
|
109
|
+
|
110
|
+
Copyright (C) 2012 Peter Zotov <whitequark@whitequark.org>
|
111
|
+
|
112
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
113
|
+
this software and associated documentation files (the "Software"), to deal in
|
114
|
+
the Software without restriction, including without limitation the rights to
|
115
|
+
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
116
|
+
of the Software, and to permit persons to whom the Software is furnished to do
|
117
|
+
so, subject to the following conditions:
|
118
|
+
|
119
|
+
The above copyright notice and this permission notice shall be included in all
|
120
|
+
copies or substantial portions of the Software.
|
121
|
+
|
122
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
123
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
124
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
125
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
126
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
127
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
128
|
+
SOFTWARE.
|
data/Rakefile
CHANGED
@@ -1 +1,21 @@
|
|
1
1
|
require "bundler/gem_tasks"
|
2
|
+
|
3
|
+
Dir.chdir File.dirname(__FILE__)
|
4
|
+
|
5
|
+
gemspec = Bundler.load_gemspec Dir["{,*}.gemspec"].first
|
6
|
+
|
7
|
+
task :release => :prepare_for_release
|
8
|
+
|
9
|
+
task :prepare_for_release do
|
10
|
+
readme = File.read('README.yard.md')
|
11
|
+
readme.gsub! /{([[:alnum:]:]+)}/i do |match|
|
12
|
+
%Q|[#{$1}](http://rubydoc.info/gems/#{gemspec.name}/#{gemspec.version}/#{$1.gsub '::', '/'})|
|
13
|
+
end
|
14
|
+
|
15
|
+
File.open('README.md', 'w') do |f|
|
16
|
+
f.write readme
|
17
|
+
end
|
18
|
+
|
19
|
+
%x|git add README.md|
|
20
|
+
#%x|git commit -m "Bump version to #{gemspec.version}."|
|
21
|
+
end
|
data/heimdallr.gemspec
CHANGED
@@ -1,16 +1,15 @@
|
|
1
1
|
# -*- encoding: utf-8 -*-
|
2
2
|
$:.push File.expand_path("../lib", __FILE__)
|
3
|
-
require "heimdallr/version"
|
4
3
|
|
5
4
|
Gem::Specification.new do |s|
|
6
5
|
s.name = "heimdallr"
|
7
|
-
s.version =
|
6
|
+
s.version = "0.0.2"
|
8
7
|
s.authors = ["Peter Zotov"]
|
9
8
|
s.email = ["whitequark@whitequark.org"]
|
10
9
|
s.homepage = "http://github.com/roundlake/heimdallr"
|
11
10
|
s.summary = %q{Heimdallr is an ActiveModel extension which provides object- and field-level access control.}
|
12
11
|
s.description = %q{Heimdallr aims to provide an easy to configure and efficient object- and field-level access
|
13
|
-
control solution, reusing proven patterns from gems like CanCan and allowing one to
|
12
|
+
control solution, reusing proven patterns from gems like CanCan and allowing one to manage permissions in a very
|
14
13
|
fine-grained manner.}
|
15
14
|
|
16
15
|
s.rubyforge_project = "heimdallr"
|
data/lib/heimdallr.rb
CHANGED
@@ -3,7 +3,44 @@ require "active_model"
|
|
3
3
|
|
4
4
|
require "heimdallr/version"
|
5
5
|
|
6
|
+
require "heimdallr/proxy/collection"
|
7
|
+
require "heimdallr/proxy/record"
|
8
|
+
require "heimdallr/validator"
|
6
9
|
require "heimdallr/evaluator"
|
7
|
-
require "heimdallr/proxy"
|
8
10
|
require "heimdallr/model"
|
9
|
-
require "heimdallr/resource"
|
11
|
+
require "heimdallr/resource"
|
12
|
+
|
13
|
+
# See {file:README.yard}.
|
14
|
+
module Heimdallr
|
15
|
+
class << self
|
16
|
+
# Allow implicit insecure association access. Consider this code:
|
17
|
+
#
|
18
|
+
# class User < ActiveRecord::Base
|
19
|
+
# include Heimdallr::Model
|
20
|
+
#
|
21
|
+
# has_many :articles
|
22
|
+
# end
|
23
|
+
#
|
24
|
+
# class Article < ActiveRecord::Base
|
25
|
+
# # No Heimdallr::Model!
|
26
|
+
# end
|
27
|
+
#
|
28
|
+
# If the +allow_insecure_associations+ setting is +false+ (the default),
|
29
|
+
# then +user.restrict(context).articles+ fetch would cause an
|
30
|
+
# {InsecureOperationError}. This may be undesirable in some environments;
|
31
|
+
# setting +allow_insecure_associations+ to +true+ will prevent the error
|
32
|
+
# from being raised.
|
33
|
+
#
|
34
|
+
# @return [Boolean]
|
35
|
+
attr_accessor :allow_insecure_associations
|
36
|
+
self.allow_insecure_associations = false
|
37
|
+
end
|
38
|
+
|
39
|
+
# {PermissionError} is raised when a security policy prevents
|
40
|
+
# a called operation from being executed.
|
41
|
+
class PermissionError < StandardError; end
|
42
|
+
|
43
|
+
# {InsecureOperationError} is raised when a potentially unsafe
|
44
|
+
# operation is about to be executed.
|
45
|
+
class InsecureOperationError < StandardError; end
|
46
|
+
end
|
data/lib/heimdallr/evaluator.rb
CHANGED
@@ -1,67 +1,149 @@
|
|
1
1
|
module Heimdallr
|
2
|
+
# Evaluator is a DSL for managing permissions on records with the field granularity.
|
3
|
+
# It works by evaluating a block of code within a given <em>security context</em>.
|
4
|
+
#
|
5
|
+
# The default resolution is to forbid everything--that is, Heimdallr security policy
|
6
|
+
# is whitelisting safe actions, not blacklisting unsafe ones. This is by design
|
7
|
+
# and is not going to change.
|
8
|
+
#
|
9
|
+
# The DSL consists of three functions: {#scope}, {#can} and {#cannot}.
|
2
10
|
class Evaluator
|
3
|
-
attr_reader :
|
11
|
+
attr_reader :allowed_fields, :fixtures, :validators
|
4
12
|
|
5
|
-
|
13
|
+
# Create a new Evaluator for the ActiveModel-descending class +model_class+,
|
14
|
+
# and use +block+ to infer restrictions for any security context passed.
|
15
|
+
def initialize(model_class, block)
|
6
16
|
@model_class, @block = model_class, block
|
7
17
|
|
8
|
-
@
|
9
|
-
@
|
18
|
+
@scopes = {}
|
19
|
+
@allowed_fields = {}
|
20
|
+
@validations = {}
|
21
|
+
@fixtures = {}
|
10
22
|
end
|
11
23
|
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
24
|
+
# @group DSL
|
25
|
+
|
26
|
+
# Define a scope. A special +:fetch+ scope is applied to any other scope
|
27
|
+
# automatically.
|
28
|
+
#
|
29
|
+
# @overload scope(name, block)
|
30
|
+
# This form accepts an explicit lambda.
|
31
|
+
#
|
32
|
+
# @example
|
33
|
+
# scope :fetch, -> { where(:protected => false) }
|
34
|
+
#
|
35
|
+
# @overload scope(name)
|
36
|
+
# This form accepts an implicit lambda.
|
37
|
+
#
|
38
|
+
# @example
|
39
|
+
# scope :fetch do
|
40
|
+
# if user.manager?
|
41
|
+
# scoped
|
42
|
+
# else
|
43
|
+
# where(:invisible => false)
|
44
|
+
# end
|
45
|
+
# end
|
46
|
+
def scope(name, explicit_block, &implicit_block)
|
47
|
+
@scopes[name] = explicit_block || implicit_block
|
48
|
+
end
|
21
49
|
|
22
|
-
|
50
|
+
# Define allowed operations for action(s).
|
51
|
+
#
|
52
|
+
# The +fields+ parameter accepts both Arrays and Hashes.
|
53
|
+
# * If an +Array+ is passed, then all fields present in the array are whitelised.
|
54
|
+
# * If a +Hash+ is passed, then all fields present as hash keys are whitelisted, and:
|
55
|
+
# 1. If a corresponding value is a +Hash+, it will be processed as a security
|
56
|
+
# validator. Security validators make records invalid when they are saved through
|
57
|
+
# a {Proxy::Record}.
|
58
|
+
# 2. If the corresponding value is any other object, it will be added as a security
|
59
|
+
# fixture. Fixtures are merged when objects are created through restricted scopes,
|
60
|
+
# and cause exceptions to be raised when a record is saved, even through the +#save+
|
61
|
+
# method.
|
62
|
+
#
|
63
|
+
# @example Array of fields
|
64
|
+
# can :view, [:title, :content]
|
65
|
+
#
|
66
|
+
# @example Fixtures
|
67
|
+
# can :create, { owner: current_user }
|
68
|
+
#
|
69
|
+
# @example Validations
|
70
|
+
# can [:create, :update], { priority: { inclusion: 1..10 } }
|
71
|
+
#
|
72
|
+
# @param [Symbol, Array<Symbol>] actions one or more action names
|
73
|
+
# @param [Hash<Hash, Object>] fields field restrictions
|
74
|
+
def can(actions, fields=@model_class.attribute_names)
|
75
|
+
Array(actions).each do |action|
|
76
|
+
case fields
|
77
|
+
when Hash # a list of validations
|
78
|
+
@allowed_fields[action] += fields.keys
|
79
|
+
@validations[action] += create_validators(fields)
|
80
|
+
@fixtures[action].merge extract_fixtures(fields)
|
81
|
+
|
82
|
+
else # an array or a field name
|
83
|
+
@allowed_fields[action] += Array(fields)
|
84
|
+
end
|
23
85
|
end
|
24
|
-
|
25
|
-
self
|
26
86
|
end
|
27
87
|
|
28
|
-
|
29
|
-
|
30
|
-
|
88
|
+
# Revoke a permission on fields.
|
89
|
+
#
|
90
|
+
# @todo Revoke validating restrictions.
|
91
|
+
# @param [Symbol, Array<Symbol>] actions one or more action names
|
92
|
+
# @param [Array<Symbol>] fields field list
|
93
|
+
def cannot(actions, fields)
|
94
|
+
Array(actions).each do |action|
|
95
|
+
@allowed_fields[action] -= fields
|
96
|
+
@fixtures.delete_at *fields
|
31
97
|
end
|
32
98
|
end
|
33
99
|
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
end
|
100
|
+
# @endgroup
|
101
|
+
|
102
|
+
# Request a scope.
|
103
|
+
#
|
104
|
+
# @param scope name of the scope
|
105
|
+
# @param basic_scope the scope to which scope +name+ will be applied. Defaults to +:fetch+.
|
106
|
+
#
|
107
|
+
# @return ActiveRecord scope
|
108
|
+
def request_scope(name=:fetch, basic_scope=request_scope(:fetch))
|
109
|
+
if name == :fetch || !@scopes.has_key?(name)
|
110
|
+
fetch_scope = @model_class.instance_exec(&@scopes[:fetch])
|
111
|
+
else
|
112
|
+
basic_scope.instance_exec(&@scopes[name])
|
48
113
|
end
|
49
114
|
end
|
50
115
|
|
51
|
-
|
52
|
-
|
116
|
+
# Compute the restrictions for a given +context+. Invokes a +block+ passed to the
|
117
|
+
# +initialize+ once.
|
118
|
+
def evaluate(context)
|
119
|
+
if context != @last_context
|
120
|
+
@scopes = {}
|
121
|
+
@allowed_fields = Hash.new { [] }
|
122
|
+
@validators = Hash.new { [] }
|
123
|
+
@fixtures = Hash.new { [] }
|
124
|
+
|
125
|
+
instance_exec context, &block
|
53
126
|
|
54
|
-
|
55
|
-
|
127
|
+
[@scopes, @allowed_fields, @validators, @fixtures].
|
128
|
+
map(&:freeze)
|
129
|
+
|
130
|
+
@last_context = context
|
56
131
|
end
|
132
|
+
|
133
|
+
self
|
57
134
|
end
|
58
135
|
|
59
136
|
protected
|
60
137
|
|
61
|
-
|
62
|
-
|
138
|
+
# Create validators for +fields+ in +ActiveModel::Validations+-like way.
|
139
|
+
#
|
140
|
+
# @return [Array<ActiveModel::Validator>]
|
141
|
+
def create_validators(fields)
|
142
|
+
validators = {}
|
63
143
|
|
64
144
|
fields.each do |attribute, validations|
|
145
|
+
next unless validations.is_a? Hash
|
146
|
+
|
65
147
|
validations.each do |key, options|
|
66
148
|
key = "#{key.to_s.camelize}Validator"
|
67
149
|
|
@@ -71,14 +153,30 @@ module Heimdallr
|
|
71
153
|
raise ArgumentError, "Unknown validator: '#{key}'"
|
72
154
|
end
|
73
155
|
|
74
|
-
validators
|
156
|
+
validators[attribute] = validator.new(_parse_validates_options(options).merge(:attributes => [ attribute ]))
|
75
157
|
end
|
76
158
|
end
|
77
159
|
|
78
160
|
validators
|
79
161
|
end
|
80
162
|
|
81
|
-
|
163
|
+
# Collects fixtures from the +fields+ definition.
|
164
|
+
def extract_fixtures(fields)
|
165
|
+
fixtures = {}
|
166
|
+
|
167
|
+
fields.each do |attribute, options|
|
168
|
+
next if options.is_a? Hash
|
169
|
+
|
170
|
+
fixtures[attribute] = options
|
171
|
+
end
|
172
|
+
|
173
|
+
fixtures
|
174
|
+
end
|
175
|
+
|
176
|
+
private
|
177
|
+
|
178
|
+
# Monkey-copied from ActiveRecord.
|
179
|
+
def _parse_validates_options(options)
|
82
180
|
case options
|
83
181
|
when TrueClass
|
84
182
|
{}
|