heimdallr 1.0.0.RC2 → 1.0.0
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.
- data/.gitignore +2 -1
- data/README.md +35 -14
- data/Rakefile +5 -19
- data/heimdallr.gemspec +1 -1
- data/lib/heimdallr/evaluator.rb +16 -11
- data/lib/heimdallr/model.rb +3 -3
- data/lib/heimdallr/proxy/record.rb +17 -2
- data/spec/proxy_spec.rb +121 -1
- data/spec/spec_helper.rb +19 -2
- data/tmp/.gitkeep +0 -0
- data/tmp/database.yml +5 -0
- metadata +19 -16
- data/README.yard.md +0 -126
data/.gitignore
CHANGED
data/README.md
CHANGED
@@ -16,24 +16,33 @@ class Article < ActiveRecord::Base
|
|
16
16
|
|
17
17
|
belongs_to :owner, :class_name => 'User'
|
18
18
|
|
19
|
-
restrict do |user|
|
20
|
-
if user.admin?
|
19
|
+
restrict do |user, record|
|
20
|
+
if user.admin?
|
21
21
|
# Administrator or owner can do everything
|
22
22
|
scope :fetch
|
23
|
-
scope :
|
23
|
+
scope :delete
|
24
24
|
can [:view, :create, :update]
|
25
25
|
else
|
26
|
-
# Other users can view only non-classified articles...
|
27
|
-
scope :fetch,
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
26
|
+
# Other users can view only their own or non-classified articles...
|
27
|
+
scope :fetch, -> { where('owner_id = ? or secrecy_level < ?', user.id, 5) }
|
28
|
+
scope :delete, -> { where('owner_id = ?', user.id) }
|
29
|
+
|
30
|
+
# ... and see all fields except the actual security level
|
31
|
+
# (through owners can see everything)...
|
32
|
+
if record.try(:owner) == user
|
33
|
+
can :view
|
34
|
+
can :update, {
|
35
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
36
|
+
}
|
37
|
+
else
|
38
|
+
can :view
|
39
|
+
cannot :view, [:secrecy_level]
|
40
|
+
end
|
32
41
|
|
33
42
|
# ... and can create them with certain restrictions.
|
34
43
|
can :create, %w(content)
|
35
|
-
can
|
36
|
-
|
44
|
+
can :create, {
|
45
|
+
owner_id: user.id,
|
37
46
|
secrecy_level: { inclusion: { in: 0..4 } }
|
38
47
|
}
|
39
48
|
end
|
@@ -85,7 +94,7 @@ secure.find 2
|
|
85
94
|
# -- No, it is not.
|
86
95
|
```
|
87
96
|
|
88
|
-
The DSL is described in documentation for [Heimdallr::Model](http://rubydoc.info/gems/heimdallr/
|
97
|
+
The DSL is described in documentation for [Heimdallr::Model](http://rubydoc.info/gems/heimdallr/master/Heimdallr/Model).
|
89
98
|
|
90
99
|
Ideology
|
91
100
|
--------
|
@@ -94,8 +103,18 @@ Heimdallr aims to make security explicit, but nevertheless convenient. It does n
|
|
94
103
|
implicit operations which may be used maliciously; instead, it forces you to explicitly call `#insecure`
|
95
104
|
method which returns the underlying object. This single point of entry is easily recognizable with code.
|
96
105
|
|
97
|
-
Heimdallr
|
98
|
-
|
106
|
+
Heimdallr has two restrictions strategies: explicit and implicit. By default it will use explicit strategy
|
107
|
+
that means it will raise an exception for every insecure request. Calling `.implicit` will give you a copy
|
108
|
+
of proxy object switched to another strategy. With that it will silently return nil for every attribute
|
109
|
+
that is inaccessible.
|
110
|
+
|
111
|
+
Typical cases
|
112
|
+
-------------
|
113
|
+
|
114
|
+
While working with MVC you'll mostly use Heimdallr-wrapped models inside your controllers and views. To
|
115
|
+
protect your controllers using DSL from the model you can use [Heimdallr::Resource](http://github.com/roundlake/heimdallr-resource) extension gem.
|
116
|
+
|
117
|
+
To facilitate views you can use `implicit` strategy which is described above.
|
99
118
|
|
100
119
|
Compatibility
|
101
120
|
-------------
|
@@ -107,6 +126,8 @@ Licensing
|
|
107
126
|
|
108
127
|
Copyright (C) 2012 Peter Zotov <whitequark@whitequark.org>
|
109
128
|
|
129
|
+
Funded by Round Lake.
|
130
|
+
|
110
131
|
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
111
132
|
this software and associated documentation files (the "Software"), to deal in
|
112
133
|
the Software without restriction, including without limitation the rights to
|
data/Rakefile
CHANGED
@@ -1,21 +1,7 @@
|
|
1
|
-
require
|
1
|
+
require 'bundler/gem_tasks'
|
2
|
+
require 'rspec/core/rake_task'
|
2
3
|
|
3
|
-
|
4
|
+
RSpec::Core::RakeTask.new(:spec)
|
4
5
|
|
5
|
-
|
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
|
6
|
+
desc "Default: run the unit tests."
|
7
|
+
task :default => [:spec]
|
data/heimdallr.gemspec
CHANGED
@@ -3,7 +3,7 @@ $:.push File.expand_path("../lib", __FILE__)
|
|
3
3
|
|
4
4
|
Gem::Specification.new do |s|
|
5
5
|
s.name = "heimdallr"
|
6
|
-
s.version = "1.0.0
|
6
|
+
s.version = "1.0.0"
|
7
7
|
s.authors = ["Peter Zotov", "Boris Staal"]
|
8
8
|
s.email = ["whitequark@whitequark.org", "boris@roundlake.ru"]
|
9
9
|
s.homepage = "http://github.com/roundlake/heimdallr"
|
data/lib/heimdallr/evaluator.rb
CHANGED
@@ -28,8 +28,9 @@ module Heimdallr
|
|
28
28
|
# Define a scope. A special +:fetch+ scope is applied to any other scope
|
29
29
|
# automatically.
|
30
30
|
#
|
31
|
-
# @overload scope(name, block)
|
32
|
-
# This form accepts an explicit lambda.
|
31
|
+
# @overload scope(name, block=nil)
|
32
|
+
# This form accepts an explicit lambda. If omitted, a scope will be
|
33
|
+
# defined to include all possible records.
|
33
34
|
#
|
34
35
|
# @example
|
35
36
|
# scope :fetch, -> { where(:protected => false) }
|
@@ -45,8 +46,12 @@ module Heimdallr
|
|
45
46
|
# where(:invisible => false)
|
46
47
|
# end
|
47
48
|
# end
|
48
|
-
def scope(name, explicit_block, &implicit_block)
|
49
|
-
|
49
|
+
def scope(name, explicit_block=nil, &implicit_block)
|
50
|
+
unless [:fetch, :delete].include?(name)
|
51
|
+
raise "There is no such scope as #{name}"
|
52
|
+
end
|
53
|
+
|
54
|
+
@scopes[name] = explicit_block || implicit_block || -> { scoped }
|
50
55
|
end
|
51
56
|
|
52
57
|
# Define allowed operations for action(s).
|
@@ -98,7 +103,7 @@ module Heimdallr
|
|
98
103
|
def cannot(actions, fields)
|
99
104
|
Array(actions).map(&:to_sym).each do |action|
|
100
105
|
@allowed_fields[action] -= fields.map(&:to_sym)
|
101
|
-
@fixtures.
|
106
|
+
fields.each { |field| @fixtures.delete field }
|
102
107
|
end
|
103
108
|
end
|
104
109
|
|
@@ -140,12 +145,12 @@ module Heimdallr
|
|
140
145
|
}
|
141
146
|
end
|
142
147
|
|
143
|
-
# Compute the restrictions for a given +context
|
144
|
-
# +initialize+ once.
|
148
|
+
# Compute the restrictions for a given +context+ and possibly a specific +record+.
|
149
|
+
# Invokes a +block+ passed to the +initialize+ once.
|
145
150
|
#
|
146
151
|
# @raise [RuntimeError] if the evaluated block did not define a set of valid restrictions
|
147
|
-
def evaluate(context)
|
148
|
-
if context != @last_context
|
152
|
+
def evaluate(context, record=nil)
|
153
|
+
if [context, record] != @last_context
|
149
154
|
@scopes = {}
|
150
155
|
@allowed_fields = Hash.new { [] }
|
151
156
|
@validators = Hash.new { [] }
|
@@ -153,7 +158,7 @@ module Heimdallr
|
|
153
158
|
|
154
159
|
@allowed_fields[:view] += [ :id ]
|
155
160
|
|
156
|
-
instance_exec context, &@block
|
161
|
+
instance_exec context, record, &@block
|
157
162
|
|
158
163
|
unless @scopes[:fetch]
|
159
164
|
raise RuntimeError, "A :fetch scope must be defined"
|
@@ -166,7 +171,7 @@ module Heimdallr
|
|
166
171
|
[@scopes, @allowed_fields, @validators, @fixtures].
|
167
172
|
map(&:freeze)
|
168
173
|
|
169
|
-
@last_context = context
|
174
|
+
@last_context = [context, record]
|
170
175
|
end
|
171
176
|
|
172
177
|
self
|
data/lib/heimdallr/model.rb
CHANGED
@@ -39,11 +39,11 @@ module Heimdallr
|
|
39
39
|
end
|
40
40
|
end
|
41
41
|
|
42
|
-
# Evaluate the restrictions for a given +context+.
|
42
|
+
# Evaluate the restrictions for a given +context+ and +record+.
|
43
43
|
#
|
44
44
|
# @return [Evaluator]
|
45
|
-
def restrictions(context)
|
46
|
-
@restrictions.evaluate(context)
|
45
|
+
def restrictions(context, record=nil)
|
46
|
+
@restrictions.evaluate(context, record)
|
47
47
|
end
|
48
48
|
|
49
49
|
# @api private
|
@@ -18,7 +18,7 @@ module Heimdallr
|
|
18
18
|
def initialize(context, record, options={})
|
19
19
|
@context, @record, @options = context, record, options
|
20
20
|
|
21
|
-
@restrictions = @record.class.restrictions(context)
|
21
|
+
@restrictions = @record.class.restrictions(context, record)
|
22
22
|
end
|
23
23
|
|
24
24
|
# @method decrement(field, by=1)
|
@@ -105,8 +105,10 @@ module Heimdallr
|
|
105
105
|
class_eval(<<-EOM, __FILE__, __LINE__)
|
106
106
|
def #{method}
|
107
107
|
scope = @restrictions.request_scope(:delete)
|
108
|
-
if scope.where({ @record.class.primary_key => @record.to_key }).
|
108
|
+
if scope.where({ @record.class.primary_key => @record.to_key }).any?
|
109
109
|
@record.#{method}
|
110
|
+
else
|
111
|
+
raise PermissionError, "Deletion is forbidden"
|
110
112
|
end
|
111
113
|
end
|
112
114
|
EOM
|
@@ -128,6 +130,10 @@ module Heimdallr
|
|
128
130
|
# @macro delegate
|
129
131
|
delegate :assign_attributes, :to => :@record
|
130
132
|
|
133
|
+
# @method attributes=
|
134
|
+
# @macro delegate
|
135
|
+
delegate :attributes=, :to => :@record
|
136
|
+
|
131
137
|
# Class name of the underlying model.
|
132
138
|
# @return [String]
|
133
139
|
def class_name
|
@@ -246,6 +252,15 @@ module Heimdallr
|
|
246
252
|
}.merge(@restrictions.reflection)
|
247
253
|
end
|
248
254
|
|
255
|
+
def modifiable?
|
256
|
+
@restrictions.can? :update
|
257
|
+
end
|
258
|
+
|
259
|
+
def destroyable?
|
260
|
+
scope = @restrictions.request_scope(:delete)
|
261
|
+
scope.where({ @record.class.primary_key => @record.to_key }).any?
|
262
|
+
end
|
263
|
+
|
249
264
|
protected
|
250
265
|
|
251
266
|
# Raises an exception if any of the changed attributes are not valid
|
data/spec/proxy_spec.rb
CHANGED
@@ -1,5 +1,125 @@
|
|
1
1
|
require 'spec_helper'
|
2
2
|
|
3
|
+
class User < ActiveRecord::Base; end
|
4
|
+
|
5
|
+
class Article < ActiveRecord::Base
|
6
|
+
include Heimdallr::Model
|
7
|
+
|
8
|
+
belongs_to :owner, :class_name => 'User'
|
9
|
+
|
10
|
+
restrict do |user, record|
|
11
|
+
if user.admin?
|
12
|
+
# Administrator or owner can do everything
|
13
|
+
scope :fetch
|
14
|
+
scope :delete
|
15
|
+
can [:view, :create, :update]
|
16
|
+
else
|
17
|
+
# Other users can view only their own or non-classified articles...
|
18
|
+
scope :fetch, -> { where('owner_id = ? or secrecy_level < ?', user.id, 5) }
|
19
|
+
scope :delete, -> { where('owner_id = ?', user.id) }
|
20
|
+
|
21
|
+
# ... and see all fields except the actual security level
|
22
|
+
# (through owners can see everything)...
|
23
|
+
if record.try(:owner) == user
|
24
|
+
can :view
|
25
|
+
can :update, {
|
26
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
27
|
+
}
|
28
|
+
else
|
29
|
+
can :view
|
30
|
+
cannot :view, [:secrecy_level]
|
31
|
+
end
|
32
|
+
|
33
|
+
# ... and can create them with certain restrictions.
|
34
|
+
can :create, %w(content)
|
35
|
+
can :create, {
|
36
|
+
owner_id: user.id,
|
37
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
38
|
+
}
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
42
|
+
|
3
43
|
describe Heimdallr::Proxy do
|
4
|
-
|
44
|
+
before(:all) do
|
45
|
+
@john = User.create! :admin => false
|
46
|
+
Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 10
|
47
|
+
Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 3
|
48
|
+
end
|
49
|
+
|
50
|
+
before(:each) do
|
51
|
+
@admin = User.new :admin => true
|
52
|
+
@looser = User.new :admin => false
|
53
|
+
end
|
54
|
+
|
55
|
+
it "should apply restrictions" do
|
56
|
+
proxy = Article.restrict(@admin)
|
57
|
+
proxy.should be_a_kind_of Heimdallr::Proxy::Collection
|
58
|
+
|
59
|
+
proxy = Article.restrict(@looser)
|
60
|
+
proxy.should be_a_kind_of Heimdallr::Proxy::Collection
|
61
|
+
end
|
62
|
+
|
63
|
+
it "should handle fetch scope" do
|
64
|
+
Article.restrict(@admin).all.count.should == 2
|
65
|
+
Article.restrict(@looser).all.count.should == 1
|
66
|
+
Article.restrict(@john).all.count.should == 2
|
67
|
+
end
|
68
|
+
|
69
|
+
it "should handle destroy scope" do
|
70
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 0
|
71
|
+
expect { article.restrict(@looser).destroy }.should raise_error
|
72
|
+
expect { article.restrict(@john).destroy }.should_not raise_error
|
73
|
+
|
74
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 0
|
75
|
+
expect { article.restrict(@admin).destroy }.should_not raise_error
|
76
|
+
end
|
77
|
+
|
78
|
+
it "should handle list of fields to view" do
|
79
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 0
|
80
|
+
expect { article.restrict(@looser).secrecy_level }.should raise_error
|
81
|
+
expect { article.restrict(@admin).secrecy_level }.should_not raise_error
|
82
|
+
expect { article.restrict(@john).secrecy_level }.should_not raise_error
|
83
|
+
article.restrict(@looser).content.should == 'test'
|
84
|
+
end
|
85
|
+
|
86
|
+
it "should handle entities creation" do
|
87
|
+
expect { Article.restrict(@looser).create! :content => 'test', :secrecy_level => 10 }.should raise_error
|
88
|
+
|
89
|
+
article = Article.restrict(@john).create! :content => 'test', :secrecy_level => 3
|
90
|
+
article.owner_id.should == @john.id
|
91
|
+
end
|
92
|
+
|
93
|
+
it "should handle entities update" do
|
94
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 10
|
95
|
+
expect {
|
96
|
+
article.restrict(@john).update_attributes! :secrecy_level => 8
|
97
|
+
}.should raise_error
|
98
|
+
expect {
|
99
|
+
article.restrict(@looser).update_attributes! :secrecy_level => 3
|
100
|
+
}.should raise_error
|
101
|
+
expect {
|
102
|
+
article.restrict(@admin).update_attributes! :secrecy_level => 10
|
103
|
+
}.should_not raise_error
|
104
|
+
end
|
105
|
+
|
106
|
+
it "should handle implicit strategy" do
|
107
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
108
|
+
expect { article.restrict(@looser).secrecy_level }.should raise_error
|
109
|
+
article.restrict(@looser).implicit.secrecy_level.should == nil
|
110
|
+
end
|
111
|
+
|
112
|
+
it "should answer if object is modifiable" do
|
113
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
114
|
+
article.restrict(@john).modifiable?.should == true
|
115
|
+
article.restrict(@admin).modifiable?.should == true
|
116
|
+
article.restrict(@looser).modifiable?.should == false
|
117
|
+
end
|
118
|
+
|
119
|
+
it "should answer if object is destroyable" do
|
120
|
+
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
121
|
+
article.restrict(@john).destroyable?.should == true
|
122
|
+
article.restrict(@admin).destroyable?.should == true
|
123
|
+
article.restrict(@looser).destroyable?.should == false
|
124
|
+
end
|
5
125
|
end
|
data/spec/spec_helper.rb
CHANGED
@@ -1,5 +1,22 @@
|
|
1
1
|
require "heimdallr"
|
2
|
+
require "active_record"
|
3
|
+
require "sqlite3"
|
4
|
+
require "logger"
|
5
|
+
require "uri"
|
2
6
|
|
3
|
-
|
4
|
-
|
7
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
8
|
+
|
9
|
+
ActiveRecord::Base.logger = Logger.new('tmp/debug.log')
|
10
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read('tmp/database.yml'))
|
11
|
+
ActiveRecord::Base.establish_connection('test')
|
12
|
+
|
13
|
+
ActiveRecord::Base.connection.create_table(:users) do |t|
|
14
|
+
t.boolean :admin
|
5
15
|
end
|
16
|
+
|
17
|
+
ActiveRecord::Base.connection.create_table(:articles) do |t|
|
18
|
+
t.belongs_to :owner
|
19
|
+
t.text :content
|
20
|
+
t.integer :secrecy_level
|
21
|
+
t.timestamps
|
22
|
+
end
|
data/tmp/.gitkeep
ADDED
File without changes
|
data/tmp/database.yml
ADDED
metadata
CHANGED
@@ -1,8 +1,8 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heimdallr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.0
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.0
|
5
|
+
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
8
8
|
- Peter Zotov
|
@@ -10,11 +10,11 @@ authors:
|
|
10
10
|
autorequire:
|
11
11
|
bindir: bin
|
12
12
|
cert_chain: []
|
13
|
-
date: 2012-04-
|
13
|
+
date: 2012-04-03 00:00:00.000000000 Z
|
14
14
|
dependencies:
|
15
15
|
- !ruby/object:Gem::Dependency
|
16
16
|
name: activesupport
|
17
|
-
requirement: &
|
17
|
+
requirement: &70194887048460 !ruby/object:Gem::Requirement
|
18
18
|
none: false
|
19
19
|
requirements:
|
20
20
|
- - ! '>='
|
@@ -22,10 +22,10 @@ dependencies:
|
|
22
22
|
version: 3.0.0
|
23
23
|
type: :runtime
|
24
24
|
prerelease: false
|
25
|
-
version_requirements: *
|
25
|
+
version_requirements: *70194887048460
|
26
26
|
- !ruby/object:Gem::Dependency
|
27
27
|
name: activemodel
|
28
|
-
requirement: &
|
28
|
+
requirement: &70194887047820 !ruby/object:Gem::Requirement
|
29
29
|
none: false
|
30
30
|
requirements:
|
31
31
|
- - ! '>='
|
@@ -33,10 +33,10 @@ dependencies:
|
|
33
33
|
version: 3.0.0
|
34
34
|
type: :runtime
|
35
35
|
prerelease: false
|
36
|
-
version_requirements: *
|
36
|
+
version_requirements: *70194887047820
|
37
37
|
- !ruby/object:Gem::Dependency
|
38
38
|
name: rspec
|
39
|
-
requirement: &
|
39
|
+
requirement: &70194887047340 !ruby/object:Gem::Requirement
|
40
40
|
none: false
|
41
41
|
requirements:
|
42
42
|
- - ! '>='
|
@@ -44,10 +44,10 @@ dependencies:
|
|
44
44
|
version: '0'
|
45
45
|
type: :development
|
46
46
|
prerelease: false
|
47
|
-
version_requirements: *
|
47
|
+
version_requirements: *70194887047340
|
48
48
|
- !ruby/object:Gem::Dependency
|
49
49
|
name: activerecord
|
50
|
-
requirement: &
|
50
|
+
requirement: &70194887039320 !ruby/object:Gem::Requirement
|
51
51
|
none: false
|
52
52
|
requirements:
|
53
53
|
- - ! '>='
|
@@ -55,7 +55,7 @@ dependencies:
|
|
55
55
|
version: '0'
|
56
56
|
type: :development
|
57
57
|
prerelease: false
|
58
|
-
version_requirements: *
|
58
|
+
version_requirements: *70194887039320
|
59
59
|
description: ! "Heimdallr aims to provide an easy to configure and efficient object-
|
60
60
|
and field-level access\n control solution, reusing proven patterns from gems like
|
61
61
|
CanCan and allowing one to manage permissions in a very\n fine-grained manner."
|
@@ -72,7 +72,6 @@ files:
|
|
72
72
|
- Gemfile
|
73
73
|
- LICENSE
|
74
74
|
- README.md
|
75
|
-
- README.yard.md
|
76
75
|
- Rakefile
|
77
76
|
- heimdallr.gemspec
|
78
77
|
- lib/heimdallr.rb
|
@@ -84,6 +83,8 @@ files:
|
|
84
83
|
- lib/heimdallr/validator.rb
|
85
84
|
- spec/proxy_spec.rb
|
86
85
|
- spec/spec_helper.rb
|
86
|
+
- tmp/.gitkeep
|
87
|
+
- tmp/database.yml
|
87
88
|
homepage: http://github.com/roundlake/heimdallr
|
88
89
|
licenses: []
|
89
90
|
post_install_message:
|
@@ -99,14 +100,16 @@ required_ruby_version: !ruby/object:Gem::Requirement
|
|
99
100
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
100
101
|
none: false
|
101
102
|
requirements:
|
102
|
-
- - ! '
|
103
|
+
- - ! '>='
|
103
104
|
- !ruby/object:Gem::Version
|
104
|
-
version:
|
105
|
+
version: '0'
|
105
106
|
requirements: []
|
106
107
|
rubyforge_project:
|
107
|
-
rubygems_version: 1.8.
|
108
|
+
rubygems_version: 1.8.15
|
108
109
|
signing_key:
|
109
110
|
specification_version: 3
|
110
111
|
summary: Heimdallr is an ActiveModel extension which provides object- and field-level
|
111
112
|
access control.
|
112
|
-
test_files:
|
113
|
+
test_files:
|
114
|
+
- spec/proxy_spec.rb
|
115
|
+
- spec/spec_helper.rb
|
data/README.yard.md
DELETED
@@ -1,126 +0,0 @@
|
|
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
|
-
scope :fetch
|
23
|
-
scope :destroy
|
24
|
-
can [:view, :create, :update]
|
25
|
-
else
|
26
|
-
# Other users can view only non-classified articles...
|
27
|
-
scope :fetch, -> { where('secrecy_level < ?', 5) }
|
28
|
-
|
29
|
-
# ... and see all fields except the actual security level...
|
30
|
-
can :view
|
31
|
-
cannot :view, [:secrecy_level]
|
32
|
-
|
33
|
-
# ... and can create them with certain restrictions.
|
34
|
-
can :create, %w(content)
|
35
|
-
can [:create, :update], {
|
36
|
-
owner: user,
|
37
|
-
secrecy_level: { inclusion: { in: 0..4 } }
|
38
|
-
}
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|
42
|
-
|
43
|
-
# Create some fictional data.
|
44
|
-
admin = User.create admin: true
|
45
|
-
johndoe = User.create admin: false
|
46
|
-
|
47
|
-
Article.create id: 1, owner: admin, content: "Nothing happens", secrecy_level: 0
|
48
|
-
Article.create id: 2, owner: admin, content: "This is a secret", secrecy_level: 10
|
49
|
-
Article.create id: 3, owner: johndoe, content: "Hello World"
|
50
|
-
|
51
|
-
# Get a restricted scope for the user.
|
52
|
-
secure = Article.restrict(johndoe)
|
53
|
-
|
54
|
-
# Use any ARel methods:
|
55
|
-
secure.pluck(:content)
|
56
|
-
# => ["Nothing happens", "Hello World"]
|
57
|
-
|
58
|
-
# Everything should be permitted explicitly:
|
59
|
-
secure.first.delete
|
60
|
-
# ! Heimdallr::PermissionError is raised
|
61
|
-
secure.find(1).secrecy_level
|
62
|
-
# ! Heimdallr::PermissionError is raised
|
63
|
-
|
64
|
-
# There is a helper for views to be easily written:
|
65
|
-
view_passed = secure.first.implicit
|
66
|
-
view_passed.secrecy_level
|
67
|
-
# => nil
|
68
|
-
|
69
|
-
# If only a single value is possible, it is inferred automatically:
|
70
|
-
secure.create! content: "My second article"
|
71
|
-
# => Article(id: 4, owner: johndoe, content: "My second article", security_level: 0)
|
72
|
-
|
73
|
-
# ... and cannot be changed:
|
74
|
-
secure.create! owner: admin, content: "I'm a haxx0r"
|
75
|
-
# ! Heimdallr::PermissionError is raised
|
76
|
-
|
77
|
-
# You can use any valid ActiveRecord validators, too:
|
78
|
-
secure.create! content: "Top Secret", secrecy_level: 10
|
79
|
-
# ! ActiveRecord::RecordInvalid is raised
|
80
|
-
|
81
|
-
# John Doe would not see what he is not permitted to, ever:
|
82
|
-
# -- I know that you have this classified material! It's in folder #2.
|
83
|
-
secure.find 2
|
84
|
-
# ! ActiveRecord::RecordNotFound is raised
|
85
|
-
# -- No, it is not.
|
86
|
-
```
|
87
|
-
|
88
|
-
The DSL is described in documentation for {Heimdallr::Model}.
|
89
|
-
|
90
|
-
Ideology
|
91
|
-
--------
|
92
|
-
|
93
|
-
Heimdallr aims to make security explicit, but nevertheless convenient. It does not allow one to call any
|
94
|
-
implicit operations which may be used maliciously; instead, it forces you to explicitly call `#insecure`
|
95
|
-
method which returns the underlying object. This single point of entry is easily recognizable with code.
|
96
|
-
|
97
|
-
Heimdallr would raise exceptions in all cases of forbidden or potentially unsecure access except for attribute
|
98
|
-
reading to allow for writing uncrufted code in templates (particularly [JBuilder](http://github.com/rails/jbuilder) ones).
|
99
|
-
|
100
|
-
Compatibility
|
101
|
-
-------------
|
102
|
-
|
103
|
-
Ruby 1.8 and ActiveRecord versions prior to 3.0 are not supported.
|
104
|
-
|
105
|
-
Licensing
|
106
|
-
---------
|
107
|
-
|
108
|
-
Copyright (C) 2012 Peter Zotov <whitequark@whitequark.org>
|
109
|
-
|
110
|
-
Permission is hereby granted, free of charge, to any person obtaining a copy of
|
111
|
-
this software and associated documentation files (the "Software"), to deal in
|
112
|
-
the Software without restriction, including without limitation the rights to
|
113
|
-
use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
|
114
|
-
of the Software, and to permit persons to whom the Software is furnished to do
|
115
|
-
so, subject to the following conditions:
|
116
|
-
|
117
|
-
The above copyright notice and this permission notice shall be included in all
|
118
|
-
copies or substantial portions of the Software.
|
119
|
-
|
120
|
-
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
121
|
-
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
122
|
-
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
123
|
-
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
124
|
-
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
125
|
-
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
126
|
-
SOFTWARE.
|