heimdallr 1.0.4 → 1.0.6

Sign up to get free protection for your applications and to get access to all the features.
@@ -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/.gitignore CHANGED
@@ -1,4 +1,5 @@
1
1
  *.gem
2
+ .rbx
2
3
  .bundle
3
4
  .yardoc
4
5
  Gemfile.lock
@@ -0,0 +1,5 @@
1
+ rvm:
2
+ - 1.9.3
3
+ - jruby-19mode
4
+ - rbx-19mode
5
+ - 2.0.0
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", security_level: 0)
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
- <img src="http://roundlake.ru/assets/logo.png" align="right" />
139
-
140
- * Peter Zotov ([@whitequark](http://twitter.com/#!/whitequark))
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.
@@ -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.4"
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 "rspec"
24
- s.add_development_dependency "activerecord"
24
+ s.add_development_dependency "rake"
25
25
  end
@@ -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"
@@ -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.attribute_names)
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
@@ -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
- @restrictions = Evaluator.new(self, block)
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
- @restrictions.evaluate(context, record)
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
- Proxy::Record.new(context, self, options)
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
- Proxy::Collection.new(@context, @scope.#{name}(*args), options_with_escape)
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).map do |element|
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
- @record.with_transaction_returning_status do
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
- @record.with_transaction_returning_status do
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.where({ @record.class.primary_key => @record.to_key }).any?
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
- if (@record.is_a?(ActiveRecord::Reflection) &&
193
- association = @record.class.reflect_on_association(method)) ||
194
- (!@record.class.heimdallr_relations.nil? &&
195
- @record.class.heimdallr_relations.include?(normalized_method))
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.where({ @record.class.primary_key => @record.to_key }).any?
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.where({ @record.class.primary_key => @record.to_key }).any?
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 fixtures.has_key? attribute
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
+
@@ -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
- 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')
6
+ require RUBY_PLATFORM =~ /java/ ? "activerecord-jdbc-adapter" : "sqlite3"
12
7
 
13
- ActiveRecord::Base.connection.create_table(:users) do |t|
14
- t.boolean :admin
15
- end
8
+ require "heimdallr" # need to require heimdallr after ORMs for orm_adapter to work
16
9
 
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
10
+ ROOT = File.join(File.dirname(__FILE__), '..')
@@ -0,0 +1,7 @@
1
+ test:
2
+ sessions:
3
+ default:
4
+ database: heimdallr_test
5
+ hosts:
6
+ - localhost:27017
7
+ database: heimdallr_test
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.4
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: 2012-06-01 00:00:00.000000000 Z
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: &70300668429320 !ruby/object:Gem::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: *70300668429320
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: &70300668428840 !ruby/object:Gem::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: *70300668428840
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: rspec
39
- requirement: &70300668428460 !ruby/object:Gem::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: '0'
45
- type: :development
48
+ version: 0.4.0
49
+ type: :runtime
46
50
  prerelease: false
47
- version_requirements: *70300668428460
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: activerecord
50
- requirement: &70300668428000 !ruby/object:Gem::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: *70300668428000
59
- description: ! "Heimdallr aims to provide an easy to configure and efficient object-
60
- and field-level access\n control solution, reusing proven patterns from gems like
61
- CanCan and allowing one to manage permissions in a very\n fine-grained manner."
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/proxy_spec.rb
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: 1.8.15
126
+ rubygems_version: 2.0.0
110
127
  signing_key:
111
- specification_version: 3
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/proxy_spec.rb
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
@@ -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