heimdallr 1.0.4 → 1.0.6
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +7 -0
- data/.gitignore +1 -0
- data/.travis.yml +5 -0
- data/Gemfile +7 -0
- data/README.md +33 -8
- data/heimdallr.gemspec +3 -3
- data/lib/heimdallr.rb +2 -1
- data/lib/heimdallr/evaluator.rb +2 -2
- data/lib/heimdallr/model.rb +23 -3
- data/lib/heimdallr/proxy/collection.rb +28 -4
- data/lib/heimdallr/proxy/record.rb +51 -16
- data/spec/active_record/models.rb +82 -0
- data/spec/active_record/proxy_spec.rb +14 -0
- data/spec/mongoid/models.rb +81 -0
- data/spec/mongoid/proxy_spec.rb +16 -0
- data/spec/proxy_examples.rb +147 -0
- data/spec/spec_helper.rb +4 -16
- data/tmp/mongoid.yml +7 -0
- metadata +55 -34
- data/spec/proxy_spec.rb +0 -131
checksums.yaml
ADDED
@@ -0,0 +1,7 @@
|
|
1
|
+
---
|
2
|
+
SHA1:
|
3
|
+
metadata.gz: 60739f5bc64b932c0cdbb42c355f488b709db6ab
|
4
|
+
data.tar.gz: 0b1860b9f5e4a8ea2ae77eab998233c716dae5c3
|
5
|
+
SHA512:
|
6
|
+
metadata.gz: 007c9d06814467c24dc70f42c0e11cea5d7b3a97edfacd324bfd1248b233da066e940f83a1ec23b696cc0a3ff8f2d7aa290da6f344ae81257a0782f94a1acc86
|
7
|
+
data.tar.gz: 9b3a7cff45a26e31e0d8426f909aa1ea811a9d33dc4ec1065c79db4d5d2d4ba15888a6aa2cb02330027e64889e20f973bf77233fb3c32f7cbcc153aa175751e4
|
data/.travis.yml
ADDED
data/Gemfile
CHANGED
@@ -1,4 +1,11 @@
|
|
1
1
|
source "http://rubygems.org"
|
2
2
|
|
3
|
+
gem "rspec"
|
4
|
+
gem "activerecord"
|
5
|
+
gem "mongoid"
|
6
|
+
gem 'sqlite3', :platform => :ruby
|
7
|
+
gem 'activerecord-jdbcsqlite3-adapter', :platform => :jruby
|
8
|
+
gem "pry"
|
9
|
+
|
3
10
|
# Specify your gem's dependencies in heimdallr.gemspec
|
4
11
|
gemspec
|
data/README.md
CHANGED
@@ -5,6 +5,8 @@ Heimdallr is a gem for managing security restrictions for ActiveRecord objects o
|
|
5
5
|
of it as a supercharged [CanCan](https://github.com/ryanb/cancan). Heimdallr favors whitelisting over blacklisting,
|
6
6
|
convention over configuration and is duck-type compatible with most of existing code.
|
7
7
|
|
8
|
+
![Travis CI](https://secure.travis-ci.org/roundlake/heimdallr.png)
|
9
|
+
|
8
10
|
``` ruby
|
9
11
|
# Define a typical set of models.
|
10
12
|
class User < ActiveRecord::Base
|
@@ -78,8 +80,8 @@ view_passed.secrecy_level
|
|
78
80
|
# => nil
|
79
81
|
|
80
82
|
# If only a single value is possible, it is inferred automatically:
|
81
|
-
secure.create! content: "My second article"
|
82
|
-
# => Article(id: 4, owner: johndoe, content: "My second article",
|
83
|
+
secure.create! content: "My second article", secrecy_level: 0
|
84
|
+
# => Article(id: 4, owner: johndoe, content: "My second article", secrecy_level: 0)
|
83
85
|
|
84
86
|
# ... and cannot be changed:
|
85
87
|
secure.create! owner: admin, content: "I'm a haxx0r"
|
@@ -116,9 +118,26 @@ in [Heimdallr](http://rubydoc.info/gems/heimdallr/master/Heimdallr).
|
|
116
118
|
Rails notes
|
117
119
|
-----------
|
118
120
|
|
119
|
-
As of Rails 3.2.3 attr_accessible is in whitelist mode by default. That makes no sense when using Heimdallr. To
|
121
|
+
As of Rails 3.2.3 attr_accessible is in whitelist mode by default. That makes no sense when using Heimdallr. To
|
120
122
|
turn it off set the `config.active_record.whitelist_attributes` value to false at yours `application.rb`.
|
121
123
|
|
124
|
+
Also you can not use restricted record with form helpers, but you can call `.insecure` method to get original model,
|
125
|
+
like this: `form_for(@user.insecure) do |f|`. Form helpers don't assign values anyway.
|
126
|
+
|
127
|
+
Mongoid notes
|
128
|
+
-------------
|
129
|
+
|
130
|
+
Heimdallr now has support for Mongoid. But please note that MongoDB doesn't support transactions,
|
131
|
+
so please be sure that all your assignments
|
132
|
+
are [atomic](http://docs.mongodb.org/manual/faq/developers/#how-do-i-do-transactions-and-locking-in-mongodb)
|
133
|
+
to prevent unexpected behaviour.
|
134
|
+
|
135
|
+
Depending on the way you include the Mongoid gem you might sometimes meet the following error: `undefined method 'to_adapter'`. It happens when you don't require Mongoid from your bundler but do it manually on the latter stages. In such cases you need to explicitly require the following file after Mongoid was included:
|
136
|
+
|
137
|
+
```ruby
|
138
|
+
require 'orm_adapter/adapters/mongoid'
|
139
|
+
```
|
140
|
+
|
122
141
|
Typical cases
|
123
142
|
-------------
|
124
143
|
|
@@ -132,15 +151,21 @@ Compatibility
|
|
132
151
|
|
133
152
|
Ruby 1.8 and ActiveRecord versions prior to 3.0 are not supported.
|
134
153
|
|
154
|
+
I have a nice shiny pull request...
|
155
|
+
-----------------------------------
|
156
|
+
|
157
|
+
... and it involves delegating `is_a?`, `class`, `respond_to?` or a similar core method? Congratulations, you just broke one of the core assumptions others have of Ruby object. Heimdallr proxies are _duck-type_ compatible with the records; this does not, in any way, make them of the same Ruby type.
|
158
|
+
|
159
|
+
Consider the pull request already rejected.
|
160
|
+
|
135
161
|
Credits
|
136
162
|
-------
|
137
163
|
|
138
|
-
|
139
|
-
|
140
|
-
*
|
141
|
-
* Boris Staal ([@_inossidabile](http://twitter.com/#!/_inossidabile))
|
164
|
+
* Peter Zotov ([@whitequark](http://twitter.com/whitequark))
|
165
|
+
* Boris Staal ([@_inossidabile](http://twitter.com/_inossidabile))
|
166
|
+
* Alexander Pavlenko ([@alerticus](https://twitter.com/alerticus))
|
142
167
|
|
143
168
|
LICENSE
|
144
169
|
-------
|
145
170
|
|
146
|
-
It is free software, and may be redistributed under the terms of MIT license.
|
171
|
+
It is free software, and may be redistributed under the terms of MIT license.
|
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.
|
6
|
+
s.version = "1.0.6"
|
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"
|
@@ -19,7 +19,7 @@ Gem::Specification.new do |s|
|
|
19
19
|
|
20
20
|
s.add_runtime_dependency "activesupport", '>= 3.0.0'
|
21
21
|
s.add_runtime_dependency "activemodel", '>= 3.0.0'
|
22
|
+
s.add_runtime_dependency "orm_adapter", '~> 0.4.0'
|
22
23
|
|
23
|
-
s.add_development_dependency "
|
24
|
-
s.add_development_dependency "activerecord"
|
24
|
+
s.add_development_dependency "rake"
|
25
25
|
end
|
data/lib/heimdallr.rb
CHANGED
@@ -1,6 +1,7 @@
|
|
1
1
|
require "active_support"
|
2
2
|
require "active_support/core_ext/module/delegation"
|
3
3
|
require "active_model"
|
4
|
+
require "orm_adapter"
|
4
5
|
|
5
6
|
# See {file:README.yard}.
|
6
7
|
module Heimdallr
|
@@ -61,4 +62,4 @@ require "heimdallr/proxy/record"
|
|
61
62
|
require "heimdallr/validator"
|
62
63
|
require "heimdallr/evaluator"
|
63
64
|
require "heimdallr/model"
|
64
|
-
require "heimdallr/legacy_resource"
|
65
|
+
require "heimdallr/legacy_resource"
|
data/lib/heimdallr/evaluator.rb
CHANGED
@@ -78,7 +78,7 @@ module Heimdallr
|
|
78
78
|
#
|
79
79
|
# @param [Symbol, Array<Symbol>] actions one or more action names
|
80
80
|
# @param [Hash<Hash, Object>] fields field restrictions
|
81
|
-
def can(actions, fields=@model_class.
|
81
|
+
def can(actions, fields=@model_class.to_adapter.column_names)
|
82
82
|
Array(actions).map(&:to_sym).each do |action|
|
83
83
|
case fields
|
84
84
|
when Hash # a list of validations
|
@@ -233,4 +233,4 @@ module Heimdallr
|
|
233
233
|
end
|
234
234
|
end
|
235
235
|
end
|
236
|
-
end
|
236
|
+
end
|
data/lib/heimdallr/model.rb
CHANGED
@@ -17,8 +17,13 @@ module Heimdallr
|
|
17
17
|
module Model
|
18
18
|
extend ActiveSupport::Concern
|
19
19
|
|
20
|
+
included do
|
21
|
+
class_attribute :heimdallr_restrictions, :instance_writer => false
|
22
|
+
end
|
23
|
+
|
20
24
|
# Class methods for {Heimdallr::Model}. See also +ActiveSupport::Concern+.
|
21
25
|
module ClassMethods
|
26
|
+
|
22
27
|
# @overload restrict
|
23
28
|
# Define restrictions for a model with a DSL. See {Model} overview
|
24
29
|
# for DSL documentation.
|
@@ -33,7 +38,7 @@ module Heimdallr
|
|
33
38
|
# @return [Proxy::Collection]
|
34
39
|
def restrict(context=nil, options={}, &block)
|
35
40
|
if block
|
36
|
-
|
41
|
+
self.heimdallr_restrictions = Evaluator.new(self, block)
|
37
42
|
else
|
38
43
|
Proxy::Collection.new(context, restrictions(context).request_scope(:fetch, self), options)
|
39
44
|
end
|
@@ -43,7 +48,7 @@ module Heimdallr
|
|
43
48
|
#
|
44
49
|
# @return [Evaluator]
|
45
50
|
def restrictions(context, record=nil)
|
46
|
-
|
51
|
+
heimdallr_restrictions.evaluate(context, record)
|
47
52
|
end
|
48
53
|
|
49
54
|
# @api private
|
@@ -72,13 +77,28 @@ module Heimdallr
|
|
72
77
|
self.heimdallr_relations ||= []
|
73
78
|
self.heimdallr_relations += methods.map(&:to_sym)
|
74
79
|
end
|
80
|
+
|
81
|
+
# Builds the Proxy class that should be used to wrap this model
|
82
|
+
def heimdallr_proxy_class
|
83
|
+
unless @heimdallr_proxy_class
|
84
|
+
record = self
|
85
|
+
|
86
|
+
@heimdallr_proxy_class = Class.new(Proxy::Record) do
|
87
|
+
define_singleton_method :model_name do
|
88
|
+
record.model_name
|
89
|
+
end
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
@heimdallr_proxy_class
|
94
|
+
end
|
75
95
|
end
|
76
96
|
|
77
97
|
# Return a secure proxy object for this record.
|
78
98
|
#
|
79
99
|
# @return [Record::Proxy]
|
80
100
|
def restrict(context, options={})
|
81
|
-
|
101
|
+
self.class.heimdallr_proxy_class.new(context, self, options)
|
82
102
|
end
|
83
103
|
|
84
104
|
# @api private
|
@@ -51,10 +51,13 @@ module Heimdallr
|
|
51
51
|
# @private
|
52
52
|
# @macro [attach] delegate_as_scope
|
53
53
|
# A proxy for +$1+ method which returns a restricted scope.
|
54
|
-
def self.delegate_as_scope(name)
|
54
|
+
def self.delegate_as_scope(name, conversion=false)
|
55
|
+
conversion = conversion ? "set = set.send(:#{conversion})" : ''
|
56
|
+
|
55
57
|
class_eval(<<-EOM, __FILE__, __LINE__)
|
56
58
|
def #{name}(*args)
|
57
|
-
|
59
|
+
set = @scope.#{name}(*args); #{conversion}
|
60
|
+
Proxy::Collection.new(@context, set, options_with_escape)
|
58
61
|
end
|
59
62
|
EOM
|
60
63
|
end
|
@@ -84,10 +87,14 @@ module Heimdallr
|
|
84
87
|
# @private
|
85
88
|
# @macro [attach] delegate_as_records
|
86
89
|
# A proxy for +$1+ method which returns an array of restricted records.
|
87
|
-
def self.delegate_as_records(name)
|
90
|
+
def self.delegate_as_records(name, conversion=false)
|
91
|
+
conversion = conversion ? "set = set.send(:#{conversion})" : ''
|
92
|
+
|
88
93
|
class_eval(<<-EOM, __FILE__, __LINE__)
|
89
94
|
def #{name}(*args)
|
90
|
-
@scope.#{name}(*args)
|
95
|
+
set = @scope.#{name}(*args); #{conversion}
|
96
|
+
|
97
|
+
set.map do |element|
|
91
98
|
element.restrict(@context, options_with_eager_load)
|
92
99
|
end
|
93
100
|
end
|
@@ -122,6 +129,8 @@ module Heimdallr
|
|
122
129
|
delegate_as_scope :reverse_order
|
123
130
|
delegate_as_scope :extending
|
124
131
|
|
132
|
+
delegate_as_value :klass
|
133
|
+
delegate_as_value :model_name
|
125
134
|
delegate_as_value :empty?
|
126
135
|
delegate_as_value :any?
|
127
136
|
delegate_as_value :many?
|
@@ -253,6 +262,21 @@ module Heimdallr
|
|
253
262
|
@scope
|
254
263
|
end
|
255
264
|
|
265
|
+
# Insecurely taps method saving restricted context for the result
|
266
|
+
# Method (or block) is supposed to return proper relation
|
267
|
+
#
|
268
|
+
# @return [Proxy::Collection]
|
269
|
+
def insecurely(*args, &block)
|
270
|
+
if block_given?
|
271
|
+
result = yield @scope
|
272
|
+
else
|
273
|
+
method = args.shift
|
274
|
+
result = @scope.send method, *args
|
275
|
+
end
|
276
|
+
|
277
|
+
Proxy::Collection.new(@context, result, options_with_escape)
|
278
|
+
end
|
279
|
+
|
256
280
|
# Describes the proxy and proxified scope.
|
257
281
|
#
|
258
282
|
# @return [String]
|
@@ -41,10 +41,6 @@ module Heimdallr
|
|
41
41
|
# and thus is not considered as a potential security threat.
|
42
42
|
delegate :touch, :to => :@record
|
43
43
|
|
44
|
-
# @method model_name
|
45
|
-
# @macro delegate
|
46
|
-
delegate :model_name, :to => :@record
|
47
|
-
|
48
44
|
# @method to_key
|
49
45
|
# @macro delegate
|
50
46
|
delegate :to_key, :to => :@record
|
@@ -53,6 +49,14 @@ module Heimdallr
|
|
53
49
|
# @macro delegate
|
54
50
|
delegate :to_param, :to => :@record
|
55
51
|
|
52
|
+
# @method to_partial_path
|
53
|
+
# @macro delegate
|
54
|
+
delegate :to_partial_path, :to => :@record
|
55
|
+
|
56
|
+
# @method persisted?
|
57
|
+
# @macro delegate
|
58
|
+
delegate :persisted?, :to => :@record
|
59
|
+
|
56
60
|
# A proxy for +attributes+ method which removes all attributes
|
57
61
|
# without +:view+ permission.
|
58
62
|
def attributes
|
@@ -70,7 +74,7 @@ module Heimdallr
|
|
70
74
|
#
|
71
75
|
# @raise [Heimdallr::PermissionError]
|
72
76
|
def update_attributes(attributes, options={})
|
73
|
-
|
77
|
+
try_transaction do
|
74
78
|
@record.assign_attributes(attributes, options)
|
75
79
|
save
|
76
80
|
end
|
@@ -81,7 +85,7 @@ module Heimdallr
|
|
81
85
|
#
|
82
86
|
# @raise [Heimdallr::PermissionError]
|
83
87
|
def update_attributes!(attributes, options={})
|
84
|
-
|
88
|
+
try_transaction do
|
85
89
|
@record.assign_attributes(attributes, options)
|
86
90
|
save!
|
87
91
|
end
|
@@ -118,7 +122,7 @@ module Heimdallr
|
|
118
122
|
class_eval(<<-EOM, __FILE__, __LINE__)
|
119
123
|
def #{method}
|
120
124
|
scope = @restrictions.request_scope(:delete)
|
121
|
-
if scope
|
125
|
+
if record_in_scope? scope
|
122
126
|
@record.#{method}
|
123
127
|
else
|
124
128
|
raise PermissionError, "Deletion is forbidden"
|
@@ -189,10 +193,17 @@ module Heimdallr
|
|
189
193
|
suffix = nil
|
190
194
|
end
|
191
195
|
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
@record.class.
|
196
|
+
builder_method = method.to_s.gsub(/\Abuild_/, '').to_sym
|
197
|
+
association = if @record.class.respond_to?(:reflect_on_association)
|
198
|
+
@record.class.reflect_on_association(method) ||
|
199
|
+
@record.class.reflect_on_association(builder_method)
|
200
|
+
end
|
201
|
+
pass = unless association
|
202
|
+
@record.class.heimdallr_relations.respond_to?(:include?) &&
|
203
|
+
@record.class.heimdallr_relations.include?(normalized_method)
|
204
|
+
end
|
205
|
+
|
206
|
+
if association || pass
|
196
207
|
referenced = @record.send(method, *args)
|
197
208
|
|
198
209
|
if referenced.nil?
|
@@ -204,7 +215,7 @@ module Heimdallr
|
|
204
215
|
options = @options
|
205
216
|
end
|
206
217
|
|
207
|
-
if association.collection? && @eager_loaded.include?(method)
|
218
|
+
if association && association.collection? && @eager_loaded.include?(method)
|
208
219
|
# Don't re-restrict eagerly loaded collections to not
|
209
220
|
# discard preloaded data.
|
210
221
|
Proxy::Collection.new(@context, referenced, options)
|
@@ -290,7 +301,7 @@ module Heimdallr
|
|
290
301
|
|
291
302
|
def visible?
|
292
303
|
scope = @restrictions.request_scope(:fetch)
|
293
|
-
scope
|
304
|
+
record_in_scope? scope
|
294
305
|
end
|
295
306
|
|
296
307
|
def creatable?
|
@@ -303,7 +314,7 @@ module Heimdallr
|
|
303
314
|
|
304
315
|
def destroyable?
|
305
316
|
scope = @restrictions.request_scope(:delete)
|
306
|
-
scope
|
317
|
+
record_in_scope? scope
|
307
318
|
end
|
308
319
|
|
309
320
|
protected
|
@@ -328,7 +339,9 @@ module Heimdallr
|
|
328
339
|
@record.changed.map(&:to_sym).each do |attribute|
|
329
340
|
value = @record.send attribute
|
330
341
|
|
331
|
-
if
|
342
|
+
if action == :create and attribute == :_id and @record.is_a?(Mongoid::Document)
|
343
|
+
# Everything is ok, continue (Mongoid sets _id before saving as opposed to ActiveRecord)
|
344
|
+
elsif fixtures.has_key? attribute
|
332
345
|
if fixtures[attribute] != value
|
333
346
|
raise Heimdallr::PermissionError,
|
334
347
|
"Attribute #{attribute} value (#{value}) is not equal to a fixture (#{fixtures[attribute]})"
|
@@ -366,5 +379,27 @@ module Heimdallr
|
|
366
379
|
end
|
367
380
|
end
|
368
381
|
end
|
382
|
+
|
383
|
+
def record_in_scope?(scope)
|
384
|
+
scope.where(primary_key => wrap_key(@record.to_key)).any?
|
385
|
+
end
|
386
|
+
|
387
|
+
def primary_key
|
388
|
+
@record.class.respond_to?(:primary_key) && @record.class.primary_key || :id
|
389
|
+
end
|
390
|
+
|
391
|
+
def wrap_key(key)
|
392
|
+
key.is_a?(Enumerable) ? key.first : key
|
393
|
+
end
|
394
|
+
|
395
|
+
def try_transaction
|
396
|
+
if @record.respond_to?(:with_transaction_returning_status)
|
397
|
+
@record.with_transaction_returning_status do
|
398
|
+
yield
|
399
|
+
end
|
400
|
+
else
|
401
|
+
yield
|
402
|
+
end
|
403
|
+
end
|
369
404
|
end
|
370
|
-
end
|
405
|
+
end
|
@@ -0,0 +1,82 @@
|
|
1
|
+
ActiveRecord::Base.logger = Logger.new('tmp/debug.log')
|
2
|
+
ActiveRecord::Base.configurations = YAML::load(IO.read('tmp/database.yml'))
|
3
|
+
ActiveRecord::Base.establish_connection('test')
|
4
|
+
|
5
|
+
ActiveRecord::Base.connection.create_table(:users) do |t|
|
6
|
+
t.boolean :admin
|
7
|
+
t.boolean :banned
|
8
|
+
t.belongs_to :dude
|
9
|
+
end
|
10
|
+
|
11
|
+
ActiveRecord::Base.connection.create_table(:dont_saves) do |t|
|
12
|
+
t.string :name
|
13
|
+
end
|
14
|
+
|
15
|
+
ActiveRecord::Base.connection.create_table(:articles) do |t|
|
16
|
+
t.belongs_to :owner
|
17
|
+
t.text :content
|
18
|
+
t.integer :secrecy_level
|
19
|
+
t.timestamps
|
20
|
+
end
|
21
|
+
|
22
|
+
class ActiveRecord::User < ActiveRecord::Base
|
23
|
+
include Heimdallr::Model
|
24
|
+
has_one :buddy, :class_name => self.name, :foreign_key => :dude_id
|
25
|
+
belongs_to :dude, :class_name => self.name
|
26
|
+
restrict do |user|
|
27
|
+
scope :fetch
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
31
|
+
class ActiveRecord::DontSave < ActiveRecord::Base; end
|
32
|
+
|
33
|
+
class ActiveRecord::Article < ActiveRecord::Base
|
34
|
+
include Heimdallr::Model
|
35
|
+
|
36
|
+
def self.by_id(id)
|
37
|
+
where(:id => id)
|
38
|
+
end
|
39
|
+
|
40
|
+
belongs_to :owner, :class_name => 'ActiveRecord::User'
|
41
|
+
|
42
|
+
def dont_save=(name)
|
43
|
+
ActiveRecord::DontSave.create :name => name
|
44
|
+
end
|
45
|
+
|
46
|
+
restrict do |user, record|
|
47
|
+
if user.banned?
|
48
|
+
# banned users cannot do anything
|
49
|
+
scope :fetch, -> { where('1=0') }
|
50
|
+
elsif user.admin?
|
51
|
+
# Administrator or owner can do everything
|
52
|
+
scope :fetch
|
53
|
+
scope :delete
|
54
|
+
can [:view, :create, :update]
|
55
|
+
else
|
56
|
+
# Other users can view only their own or non-classified articles...
|
57
|
+
scope :fetch, -> { where('owner_id = ? or secrecy_level < ?', user.id, 5) }
|
58
|
+
scope :delete, -> { where('owner_id = ?', user.id) }
|
59
|
+
|
60
|
+
# ... and see all fields except the actual security level
|
61
|
+
# (through owners can see everything)...
|
62
|
+
if record.try(:owner) == user
|
63
|
+
can :view
|
64
|
+
can :update, {
|
65
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
66
|
+
}
|
67
|
+
else
|
68
|
+
can :view
|
69
|
+
cannot :view, [:secrecy_level]
|
70
|
+
end
|
71
|
+
|
72
|
+
# ... and can create them with certain restrictions.
|
73
|
+
can :create, %w(content)
|
74
|
+
can :create, {
|
75
|
+
owner_id: user.id,
|
76
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
77
|
+
}
|
78
|
+
end
|
79
|
+
end
|
80
|
+
end
|
81
|
+
|
82
|
+
class ActiveRecord::SubArticle < ActiveRecord::Article; end
|
@@ -0,0 +1,14 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'active_record/models'
|
3
|
+
|
4
|
+
require 'proxy_examples'
|
5
|
+
|
6
|
+
describe Heimdallr::Proxy do
|
7
|
+
context 'with ActiveRecord' do
|
8
|
+
run_specs(ActiveRecord::User, ActiveRecord::Article, ActiveRecord::DontSave)
|
9
|
+
|
10
|
+
context 'with subclass' do
|
11
|
+
run_specs(ActiveRecord::User, ActiveRecord::SubArticle, ActiveRecord::DontSave)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
@@ -0,0 +1,81 @@
|
|
1
|
+
require 'mongoid'
|
2
|
+
require 'orm_adapter/adapters/mongoid'
|
3
|
+
|
4
|
+
ENV['RACK_ENV'] ||= 'test'
|
5
|
+
Mongoid.load!('tmp/mongoid.yml')
|
6
|
+
|
7
|
+
class Mongoid::User
|
8
|
+
include Mongoid::Document
|
9
|
+
field :admin, type: Boolean
|
10
|
+
field :banned, type: Boolean
|
11
|
+
field :dude_id, type: String
|
12
|
+
include Heimdallr::Model
|
13
|
+
has_one :buddy, :class_name => self.name, :foreign_key => :dude_id
|
14
|
+
belongs_to :dude, :class_name => self.name
|
15
|
+
restrict do |user|
|
16
|
+
scope :fetch
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
class Mongoid::DontSave
|
21
|
+
include Mongoid::Document
|
22
|
+
field :name
|
23
|
+
end
|
24
|
+
|
25
|
+
class Mongoid::Article
|
26
|
+
include Mongoid::Document
|
27
|
+
include Mongoid::Timestamps
|
28
|
+
|
29
|
+
field :content
|
30
|
+
field :secrecy_level, type: Fixnum
|
31
|
+
|
32
|
+
belongs_to :owner, class_name: 'Mongoid::User'
|
33
|
+
|
34
|
+
include Heimdallr::Model
|
35
|
+
|
36
|
+
def self.by_id(id)
|
37
|
+
where(:id => id)
|
38
|
+
end
|
39
|
+
|
40
|
+
def dont_save=(name)
|
41
|
+
# Just don't do this in Mongo!
|
42
|
+
# Mongoid::DontSave.create(:name => name)
|
43
|
+
end
|
44
|
+
|
45
|
+
restrict do |user, record|
|
46
|
+
if user.banned?
|
47
|
+
# banned users cannot do anything
|
48
|
+
scope :fetch, proc { where('1' => 0) }
|
49
|
+
elsif user.admin?
|
50
|
+
# Administrator or owner can do everything
|
51
|
+
scope :fetch
|
52
|
+
scope :delete
|
53
|
+
can [:view, :create, :update]
|
54
|
+
else
|
55
|
+
# Other users can view only their own or non-classified articles...
|
56
|
+
scope :fetch, -> { scoped.or({owner_id: user.id}, {:secrecy_level.lt => 5}) }
|
57
|
+
scope :delete, -> { where(owner_id: user.id) }
|
58
|
+
|
59
|
+
# ... and see all fields except the actual security level
|
60
|
+
# (through owners can see everything)...
|
61
|
+
if record.try(:owner) == user
|
62
|
+
can :view
|
63
|
+
can :update, {
|
64
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
65
|
+
}
|
66
|
+
else
|
67
|
+
can :view
|
68
|
+
cannot :view, [:secrecy_level]
|
69
|
+
end
|
70
|
+
|
71
|
+
# ... and can create them with certain restrictions.
|
72
|
+
can :create, %w(content _type)
|
73
|
+
can :create, {
|
74
|
+
owner_id: user.id,
|
75
|
+
secrecy_level: { inclusion: { in: 0..4 } }
|
76
|
+
}
|
77
|
+
end
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
81
|
+
class Mongoid::SubArticle < Mongoid::Article; end
|
@@ -0,0 +1,16 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'mongoid/models'
|
3
|
+
|
4
|
+
require 'proxy_examples'
|
5
|
+
|
6
|
+
if ENV['ENABLE_MONGO']
|
7
|
+
describe Heimdallr::Proxy do
|
8
|
+
context 'with Mongoid' do
|
9
|
+
run_specs(Mongoid::User, Mongoid::Article, Mongoid::DontSave)
|
10
|
+
|
11
|
+
context 'with subclass' do
|
12
|
+
run_specs(Mongoid::User, Mongoid::SubArticle, Mongoid::DontSave)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,147 @@
|
|
1
|
+
def run_specs(user_model, article_model, dont_save_model)
|
2
|
+
before(:all) do
|
3
|
+
user_model.destroy_all
|
4
|
+
article_model.destroy_all
|
5
|
+
dont_save_model.destroy_all
|
6
|
+
|
7
|
+
@john = user_model.create! :admin => false
|
8
|
+
@banned = user_model.create! :banned => true
|
9
|
+
@models = []
|
10
|
+
|
11
|
+
@models << article_model.create!(:owner_id => @john.id, :content => 'test', :secrecy_level => 10)
|
12
|
+
@models << article_model.create!(:owner_id => @john.id, :content => 'test', :secrecy_level => 3)
|
13
|
+
end
|
14
|
+
|
15
|
+
before(:each) do
|
16
|
+
@admin = user_model.new :admin => true
|
17
|
+
@looser = user_model.new :admin => false
|
18
|
+
end
|
19
|
+
|
20
|
+
it "proxies model_name for records" do
|
21
|
+
article_model.restrict(@admin).first.class.model_name.should ==
|
22
|
+
article_model.model_name
|
23
|
+
end
|
24
|
+
|
25
|
+
it "proxies model_name for collections" do
|
26
|
+
article_model.restrict(@admin).model_name.should ==
|
27
|
+
article_model.model_name
|
28
|
+
end
|
29
|
+
|
30
|
+
it "applies restrictions" do
|
31
|
+
proxy = article_model.restrict(@admin)
|
32
|
+
proxy.should be_a_kind_of Heimdallr::Proxy::Collection
|
33
|
+
|
34
|
+
proxy = article_model.restrict(@looser)
|
35
|
+
proxy.should be_a_kind_of Heimdallr::Proxy::Collection
|
36
|
+
end
|
37
|
+
|
38
|
+
it "taps insecurely" do
|
39
|
+
proxy = article_model.restrict(@admin).insecurely(:by_id, @models.first.id)
|
40
|
+
proxy.should be_a_kind_of Heimdallr::Proxy::Collection
|
41
|
+
proxy.count.should == 1
|
42
|
+
|
43
|
+
proxy = article_model.restrict(@looser).insecurely{|x| x.by_id(@models.first.id)}
|
44
|
+
proxy.should be_a_kind_of Heimdallr::Proxy::Collection
|
45
|
+
proxy.count.should == 0
|
46
|
+
end
|
47
|
+
|
48
|
+
it "handles fetch scope" do
|
49
|
+
article_model.restrict(@admin).count.should == 2
|
50
|
+
article_model.restrict(@looser).count.should == 1
|
51
|
+
article_model.restrict(@john).count.should == 2
|
52
|
+
end
|
53
|
+
|
54
|
+
it "handles destroy scope" do
|
55
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 0
|
56
|
+
expect { article.restrict(@looser).destroy }.to raise_error
|
57
|
+
expect { article.restrict(@john).destroy }.to_not raise_error
|
58
|
+
|
59
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 0
|
60
|
+
expect { article.restrict(@admin).destroy }.to_not raise_error
|
61
|
+
end
|
62
|
+
|
63
|
+
it "handles list of fields to view" do
|
64
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 0
|
65
|
+
expect { article.restrict(@looser).secrecy_level }.to raise_error
|
66
|
+
expect { article.restrict(@admin).secrecy_level }.to_not raise_error
|
67
|
+
expect { article.restrict(@john).secrecy_level }.to_not raise_error
|
68
|
+
article.restrict(@looser).id.should == article.id
|
69
|
+
article.restrict(@looser).content.should == 'test'
|
70
|
+
end
|
71
|
+
|
72
|
+
it "handles entities creation" do
|
73
|
+
expect { article_model.restrict(@looser).create! :content => 'test', :secrecy_level => 10 }.to raise_error
|
74
|
+
|
75
|
+
article = article_model.restrict(@john).create! :content => 'test', :secrecy_level => 3
|
76
|
+
article.owner_id.should == @john.id
|
77
|
+
end
|
78
|
+
|
79
|
+
it "handles entities building" do
|
80
|
+
[:build_buddy, :build_dude].map{|m| @john.restrict(@john).send(m) }.each do |obj|
|
81
|
+
obj.should be_a(Heimdallr::Proxy::Record)
|
82
|
+
obj.reflect_on_security[:context].should === @john
|
83
|
+
end
|
84
|
+
end
|
85
|
+
|
86
|
+
it "handles entities update" do
|
87
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 10
|
88
|
+
expect {
|
89
|
+
article.restrict(@john).update_attributes! :secrecy_level => 8
|
90
|
+
}.to raise_error
|
91
|
+
expect {
|
92
|
+
article.restrict(@looser).update_attributes! :secrecy_level => 3
|
93
|
+
}.to raise_error
|
94
|
+
expect {
|
95
|
+
article.restrict(@admin).update_attributes! :secrecy_level => 10
|
96
|
+
}.to_not raise_error
|
97
|
+
end
|
98
|
+
|
99
|
+
it "handles implicit strategy" do
|
100
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
101
|
+
expect { article.restrict(@looser).secrecy_level }.to raise_error
|
102
|
+
article.restrict(@looser).implicit.secrecy_level.should == nil
|
103
|
+
end
|
104
|
+
|
105
|
+
it "answers if object is creatable" do
|
106
|
+
article_model.restrict(@john).should be_creatable
|
107
|
+
article_model.restrict(@admin).should be_creatable
|
108
|
+
article_model.restrict(@looser).should be_creatable
|
109
|
+
end
|
110
|
+
|
111
|
+
it "answers if object is modifiable" do
|
112
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
113
|
+
article.restrict(@john).should be_modifiable
|
114
|
+
article.restrict(@admin).should be_modifiable
|
115
|
+
article.restrict(@looser).should_not be_modifiable
|
116
|
+
end
|
117
|
+
|
118
|
+
it "answers if object is destroyable" do
|
119
|
+
article = article_model.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
120
|
+
article.restrict(@john).should be_destroyable
|
121
|
+
article.restrict(@admin).should be_destroyable
|
122
|
+
article.restrict(@looser).should_not be_destroyable
|
123
|
+
end
|
124
|
+
|
125
|
+
it "does not create anything else if it did not saved" do
|
126
|
+
expect {
|
127
|
+
article_model.restrict(@looser).create! :content => 'test', :secrecy_level => 10, :dont_save => 'ok' rescue nil
|
128
|
+
}.not_to change(dont_save_model, :count)
|
129
|
+
end
|
130
|
+
|
131
|
+
context "when user has no rights to view" do
|
132
|
+
it "is not be visible" do
|
133
|
+
article = article_model.create! :content => 'test', :owner => @john, :secrecy_level => 0
|
134
|
+
article.restrict(@banned).should_not be_visible
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
context "when user has no rights to create" do
|
139
|
+
it "is not be creatable" do
|
140
|
+
article_model.restrict(@banned).should_not be_creatable
|
141
|
+
expect {
|
142
|
+
article_model.restrict(@banned).create! :content => 'test', :secrecy_level => 0
|
143
|
+
}.to raise_error
|
144
|
+
end
|
145
|
+
end
|
146
|
+
end
|
147
|
+
|
data/spec/spec_helper.rb
CHANGED
@@ -1,22 +1,10 @@
|
|
1
|
-
require "heimdallr"
|
2
1
|
require "active_record"
|
3
|
-
require "sqlite3"
|
4
2
|
require "logger"
|
5
3
|
require "uri"
|
4
|
+
require "pry"
|
6
5
|
|
7
|
-
|
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')
|
6
|
+
require RUBY_PLATFORM =~ /java/ ? "activerecord-jdbc-adapter" : "sqlite3"
|
12
7
|
|
13
|
-
|
14
|
-
t.boolean :admin
|
15
|
-
end
|
8
|
+
require "heimdallr" # need to require heimdallr after ORMs for orm_adapter to work
|
16
9
|
|
17
|
-
|
18
|
-
t.belongs_to :owner
|
19
|
-
t.text :content
|
20
|
-
t.integer :secrecy_level
|
21
|
-
t.timestamps
|
22
|
-
end
|
10
|
+
ROOT = File.join(File.dirname(__FILE__), '..')
|
data/tmp/mongoid.yml
ADDED
metadata
CHANGED
@@ -1,8 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: heimdallr
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.0.
|
5
|
-
prerelease:
|
4
|
+
version: 1.0.6
|
6
5
|
platform: ruby
|
7
6
|
authors:
|
8
7
|
- Peter Zotov
|
@@ -10,55 +9,68 @@ authors:
|
|
10
9
|
autorequire:
|
11
10
|
bindir: bin
|
12
11
|
cert_chain: []
|
13
|
-
date:
|
12
|
+
date: 2013-02-25 00:00:00.000000000 Z
|
14
13
|
dependencies:
|
15
14
|
- !ruby/object:Gem::Dependency
|
16
15
|
name: activesupport
|
17
|
-
requirement:
|
18
|
-
none: false
|
16
|
+
requirement: !ruby/object:Gem::Requirement
|
19
17
|
requirements:
|
20
|
-
- -
|
18
|
+
- - '>='
|
21
19
|
- !ruby/object:Gem::Version
|
22
20
|
version: 3.0.0
|
23
21
|
type: :runtime
|
24
22
|
prerelease: false
|
25
|
-
version_requirements:
|
23
|
+
version_requirements: !ruby/object:Gem::Requirement
|
24
|
+
requirements:
|
25
|
+
- - '>='
|
26
|
+
- !ruby/object:Gem::Version
|
27
|
+
version: 3.0.0
|
26
28
|
- !ruby/object:Gem::Dependency
|
27
29
|
name: activemodel
|
28
|
-
requirement:
|
29
|
-
none: false
|
30
|
+
requirement: !ruby/object:Gem::Requirement
|
30
31
|
requirements:
|
31
|
-
- -
|
32
|
+
- - '>='
|
32
33
|
- !ruby/object:Gem::Version
|
33
34
|
version: 3.0.0
|
34
35
|
type: :runtime
|
35
36
|
prerelease: false
|
36
|
-
version_requirements:
|
37
|
+
version_requirements: !ruby/object:Gem::Requirement
|
38
|
+
requirements:
|
39
|
+
- - '>='
|
40
|
+
- !ruby/object:Gem::Version
|
41
|
+
version: 3.0.0
|
37
42
|
- !ruby/object:Gem::Dependency
|
38
|
-
name:
|
39
|
-
requirement:
|
40
|
-
none: false
|
43
|
+
name: orm_adapter
|
44
|
+
requirement: !ruby/object:Gem::Requirement
|
41
45
|
requirements:
|
42
|
-
- -
|
46
|
+
- - ~>
|
43
47
|
- !ruby/object:Gem::Version
|
44
|
-
version:
|
45
|
-
type: :
|
48
|
+
version: 0.4.0
|
49
|
+
type: :runtime
|
46
50
|
prerelease: false
|
47
|
-
version_requirements:
|
51
|
+
version_requirements: !ruby/object:Gem::Requirement
|
52
|
+
requirements:
|
53
|
+
- - ~>
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: 0.4.0
|
48
56
|
- !ruby/object:Gem::Dependency
|
49
|
-
name:
|
50
|
-
requirement:
|
51
|
-
none: false
|
57
|
+
name: rake
|
58
|
+
requirement: !ruby/object:Gem::Requirement
|
52
59
|
requirements:
|
53
|
-
- -
|
60
|
+
- - '>='
|
54
61
|
- !ruby/object:Gem::Version
|
55
62
|
version: '0'
|
56
63
|
type: :development
|
57
64
|
prerelease: false
|
58
|
-
version_requirements:
|
59
|
-
|
60
|
-
|
61
|
-
|
65
|
+
version_requirements: !ruby/object:Gem::Requirement
|
66
|
+
requirements:
|
67
|
+
- - '>='
|
68
|
+
- !ruby/object:Gem::Version
|
69
|
+
version: '0'
|
70
|
+
description: |-
|
71
|
+
Heimdallr aims to provide an easy to configure and efficient object- and field-level access
|
72
|
+
control solution, reusing proven patterns from gems like CanCan and allowing one to manage permissions in a very
|
73
|
+
fine-grained manner.
|
62
74
|
email:
|
63
75
|
- whitequark@whitequark.org
|
64
76
|
- boris@roundlake.ru
|
@@ -68,6 +80,7 @@ extra_rdoc_files: []
|
|
68
80
|
files:
|
69
81
|
- .gitignore
|
70
82
|
- .rspec
|
83
|
+
- .travis.yml
|
71
84
|
- .yardopts
|
72
85
|
- CHANGELOG.md
|
73
86
|
- Gemfile
|
@@ -82,35 +95,43 @@ files:
|
|
82
95
|
- lib/heimdallr/proxy/collection.rb
|
83
96
|
- lib/heimdallr/proxy/record.rb
|
84
97
|
- lib/heimdallr/validator.rb
|
85
|
-
- spec/
|
98
|
+
- spec/active_record/models.rb
|
99
|
+
- spec/active_record/proxy_spec.rb
|
100
|
+
- spec/mongoid/models.rb
|
101
|
+
- spec/mongoid/proxy_spec.rb
|
102
|
+
- spec/proxy_examples.rb
|
86
103
|
- spec/spec_helper.rb
|
87
104
|
- tmp/.gitkeep
|
88
105
|
- tmp/database.yml
|
106
|
+
- tmp/mongoid.yml
|
89
107
|
homepage: http://github.com/roundlake/heimdallr
|
90
108
|
licenses: []
|
109
|
+
metadata: {}
|
91
110
|
post_install_message:
|
92
111
|
rdoc_options: []
|
93
112
|
require_paths:
|
94
113
|
- lib
|
95
114
|
required_ruby_version: !ruby/object:Gem::Requirement
|
96
|
-
none: false
|
97
115
|
requirements:
|
98
|
-
- -
|
116
|
+
- - '>='
|
99
117
|
- !ruby/object:Gem::Version
|
100
118
|
version: '0'
|
101
119
|
required_rubygems_version: !ruby/object:Gem::Requirement
|
102
|
-
none: false
|
103
120
|
requirements:
|
104
|
-
- -
|
121
|
+
- - '>='
|
105
122
|
- !ruby/object:Gem::Version
|
106
123
|
version: '0'
|
107
124
|
requirements: []
|
108
125
|
rubyforge_project:
|
109
|
-
rubygems_version:
|
126
|
+
rubygems_version: 2.0.0
|
110
127
|
signing_key:
|
111
|
-
specification_version:
|
128
|
+
specification_version: 4
|
112
129
|
summary: Heimdallr is an ActiveModel extension which provides object- and field-level
|
113
130
|
access control.
|
114
131
|
test_files:
|
115
|
-
- spec/
|
132
|
+
- spec/active_record/models.rb
|
133
|
+
- spec/active_record/proxy_spec.rb
|
134
|
+
- spec/mongoid/models.rb
|
135
|
+
- spec/mongoid/proxy_spec.rb
|
136
|
+
- spec/proxy_examples.rb
|
116
137
|
- spec/spec_helper.rb
|
data/spec/proxy_spec.rb
DELETED
@@ -1,131 +0,0 @@
|
|
1
|
-
require 'spec_helper'
|
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
|
-
|
43
|
-
describe Heimdallr::Proxy do
|
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 creatable" do
|
113
|
-
Article.restrict(@john).creatable?.should == true
|
114
|
-
Article.restrict(@admin).creatable?.should == true
|
115
|
-
Article.restrict(@looser).creatable?.should == true
|
116
|
-
end
|
117
|
-
|
118
|
-
it "should answer if object is modifiable" do
|
119
|
-
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
120
|
-
article.restrict(@john).modifiable?.should == true
|
121
|
-
article.restrict(@admin).modifiable?.should == true
|
122
|
-
article.restrict(@looser).modifiable?.should == false
|
123
|
-
end
|
124
|
-
|
125
|
-
it "should answer if object is destroyable" do
|
126
|
-
article = Article.create! :owner_id => @john.id, :content => 'test', :secrecy_level => 4
|
127
|
-
article.restrict(@john).destroyable?.should == true
|
128
|
-
article.restrict(@admin).destroyable?.should == true
|
129
|
-
article.restrict(@looser).destroyable?.should == false
|
130
|
-
end
|
131
|
-
end
|