protector 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ffb03e438ddf160d6def55eedad02d8666ed2c4a
4
+ data.tar.gz: 8c1cf283df6c5ddd6e0cb96dd8439331406911b4
5
+ SHA512:
6
+ metadata.gz: 83ac0169c982cfa2a839eefb4dda73c10c8432516cb2eea9919e1a337233f1c86be2fbbd53164a3da01b65e19bc929afcef50e48e3918c11348f095fe2376d1c
7
+ data.tar.gz: 5eaa624122f8c6272b0e7b2f139f83e7f12961f487a3019119f8e05bdd82b68fa2eee5d0d4ebb1888cc122aac58b080c27abf9f4a0a05eec7f34818e72e5063b
data/.gitignore ADDED
@@ -0,0 +1,17 @@
1
+ *.gem
2
+ *.rbc
3
+ .bundle
4
+ .config
5
+ .yardoc
6
+ Gemfile.lock
7
+ InstalledFiles
8
+ _yardoc
9
+ coverage
10
+ doc/
11
+ lib/bundler/man
12
+ pkg
13
+ rdoc
14
+ spec/reports
15
+ test/tmp
16
+ test/version_tmp
17
+ tmp
data/.rspec ADDED
@@ -0,0 +1,3 @@
1
+ --tty
2
+ --color
3
+ --format progress
data/Gemfile ADDED
@@ -0,0 +1,10 @@
1
+ source 'https://rubygems.org'
2
+ gemspec
3
+
4
+ gem 'rake'
5
+ gem 'pry'
6
+ gem 'rspec'
7
+ gem 'guard'
8
+ gem 'guard-rspec'
9
+
10
+ gem 'activerecord', '>= 3.0'
data/LICENSE.txt ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2013 Boris Staal
2
+
3
+ MIT License
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining
6
+ a copy of this software and associated documentation files (the
7
+ "Software"), to deal in the Software without restriction, including
8
+ without limitation the rights to use, copy, modify, merge, publish,
9
+ distribute, sublicense, and/or sell copies of the Software, and to
10
+ permit persons to whom the Software is furnished to do so, subject to
11
+ the following conditions:
12
+
13
+ The above copyright notice and this permission notice shall be
14
+ included in all copies or substantial portions of the Software.
15
+
16
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
17
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
18
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
19
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
20
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
21
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
22
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,33 @@
1
+ # Protector
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'protector'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install protector
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Contributing
24
+
25
+ 1. Fork it
26
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
27
+ 3. Commit your changes (`git commit -am 'Add some feature'`)
28
+ 4. Push to the branch (`git push origin my-new-feature`)
29
+ 5. Create new Pull Request
30
+
31
+ ## LICENSE
32
+
33
+ It is free software, and may be redistributed under the terms of MIT license.
data/Rakefile ADDED
@@ -0,0 +1,8 @@
1
+ require 'bundler/setup'
2
+ require 'bundler/gem_tasks'
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ desc "Default: run the unit tests."
8
+ task :default => :spec
@@ -0,0 +1,105 @@
1
+ module Protector
2
+ module Adapters
3
+ module ActiveRecord
4
+ def self.activate!
5
+ ::ActiveRecord::Base.send :include, Protector::Adapters::ActiveRecord::Base
6
+ ::ActiveRecord::Relation.send :include, Protector::Adapters::ActiveRecord::Relation
7
+ end
8
+
9
+ module Base
10
+ extend ActiveSupport::Concern
11
+
12
+ included do
13
+ include Protector::DSL::Base
14
+ include Protector::DSL::Entry
15
+
16
+ validate(on: :create) do
17
+ return unless @protector_subject
18
+ errors[:base] << I18n.t('protector.invalid') unless creatable?
19
+ end
20
+
21
+ validate(on: :update) do
22
+ return unless @protector_subject
23
+ errors[:base] << I18n.t('protector.invalid') unless updatable?
24
+ end
25
+
26
+ before_destroy do
27
+ return true unless @protector_subject
28
+ destroyable?
29
+ end
30
+ end
31
+
32
+ module ClassMethods
33
+ def restrict(subject)
34
+ all.restrict(subject)
35
+ end
36
+ end
37
+
38
+ def protector_meta
39
+ unless @protector_subject
40
+ raise "Unprotected entity detected: use `restrict` method to protect it."
41
+ end
42
+
43
+ self.class.protector_meta.evaluate(
44
+ self.class,
45
+ self.class.column_names,
46
+ @protector_subject,
47
+ self
48
+ )
49
+ end
50
+
51
+ def visible?
52
+ protector_meta.relation.where(
53
+ self.class.primary_key => send(self.class.primary_key)
54
+ ).any?
55
+ end
56
+
57
+ def creatable?
58
+ fields = HashWithIndifferentAccess[changed.map{|x| [x, __send__(x)]}]
59
+ protector_meta.creatable?(fields)
60
+ end
61
+
62
+ def updatable?
63
+ fields = HashWithIndifferentAccess[changed.map{|x| [x, __send__(x)]}]
64
+ protector_meta.updatable?(fields)
65
+ end
66
+
67
+ def destroyable?
68
+ protector_meta.destroyable?
69
+ end
70
+ end
71
+
72
+ module Relation
73
+ extend ActiveSupport::Concern
74
+
75
+ included do
76
+ include Protector::DSL::Base
77
+
78
+ alias_method_chain :exec_queries, :protector
79
+ end
80
+
81
+ def protector_meta
82
+ @klass.protector_meta.evaluate(@klass, @klass.column_names, @protector_subject)
83
+ end
84
+
85
+ def count
86
+ super || 0
87
+ end
88
+
89
+ def sum
90
+ super || 0
91
+ end
92
+
93
+ def calculate(*args)
94
+ return super unless @protector_subject
95
+ merge(protector_meta.relation).unrestrict.calculate *args
96
+ end
97
+
98
+ def exec_queries_with_protector(*args)
99
+ return exec_queries_without_protector unless @protector_subject
100
+ @records = merge(protector_meta.relation).unrestrict.send :exec_queries
101
+ end
102
+ end
103
+ end
104
+ end
105
+ end
@@ -0,0 +1,143 @@
1
+ module Protector
2
+ module DSL
3
+ # DSL meta storage and evaluator
4
+ class Meta
5
+ class Box
6
+ attr_accessor :access, :relation, :destroyable
7
+
8
+ def initialize(model, fields, subject, entry, blocks)
9
+ @model = model
10
+ @fields = fields
11
+ @access = {update: {}, view: {}, create: {}}.with_indifferent_access
12
+ @relation = false
13
+ @destroyable = false
14
+
15
+ blocks.each do |b|
16
+ if b.arity == 2
17
+ instance_exec subject, entry, &b
18
+ elsif b.arity == 1
19
+ instance_exec subject, &b
20
+ else
21
+ instance_exec &b
22
+ end
23
+ end
24
+ end
25
+
26
+ def scope(&block)
27
+ @relation = @model.instance_eval(&block)
28
+ end
29
+
30
+ def can(action, *fields)
31
+ return @destroyable = true if action == :destroy
32
+ return unless @access[action]
33
+
34
+ if fields.size == 0
35
+ @fields.each{|f| @access[action][f] = nil}
36
+ else
37
+ fields.each do |a|
38
+ if a.is_a?(Array)
39
+ a.each{|f| @access[action][f] = nil}
40
+ elsif a.is_a?(Hash)
41
+ @access[action].merge!(a)
42
+ else
43
+ @access[action][a] = nil
44
+ end
45
+ end
46
+ end
47
+ end
48
+
49
+ def cannot(action, *fields)
50
+ return @destroyable = false if action == :destroy
51
+ return unless @access[action]
52
+
53
+ if fields.size == 0
54
+ @access[action].clear
55
+ else
56
+ fields.each do |a|
57
+ if a.is_a?(Array)
58
+ a.each{|f| @access[action].delete(f)}
59
+ else
60
+ @access[action].delete(a)
61
+ end
62
+ end
63
+ end
64
+ end
65
+
66
+ def creatable?(fields=false)
67
+ modifiable? :create, fields
68
+ end
69
+
70
+ def updatable?(fields=false)
71
+ modifiable? :update, fields
72
+ end
73
+
74
+ def destroyable?
75
+ @destroyable
76
+ end
77
+
78
+ private
79
+
80
+ def modifiable?(part, fields)
81
+ return false unless @access[part].length > 0
82
+
83
+ if fields
84
+ return false if (fields.keys - @access[part].keys).length > 0
85
+
86
+ fields.each do |k,v|
87
+ case x = @access[part][k]
88
+ when Range
89
+ return false unless x.include?(v)
90
+ when Proc
91
+ return false unless x.call(v)
92
+ end
93
+ end
94
+ end
95
+
96
+ true
97
+ end
98
+ end
99
+
100
+ def <<(block)
101
+ (@blocks ||= []) << block
102
+ end
103
+
104
+ def evaluate(model, fields, subject, entry=nil)
105
+ Box.new(model, fields, subject, entry, @blocks)
106
+ end
107
+ end
108
+
109
+ module Base
110
+ extend ActiveSupport::Concern
111
+
112
+ included do
113
+ attr_reader :protector_subject
114
+ end
115
+
116
+ def restrict(subject)
117
+ @protector_subject = subject
118
+ self
119
+ end
120
+
121
+ def unrestrict
122
+ @protector_subject = nil
123
+ self
124
+ end
125
+ end
126
+
127
+ module Entry
128
+ extend ActiveSupport::Concern
129
+
130
+ included do
131
+ class <<self
132
+ attr_reader :protector_meta
133
+ end
134
+ end
135
+
136
+ module ClassMethods
137
+ def protect(&block)
138
+ (@protector_meta ||= Meta.new) << block
139
+ end
140
+ end
141
+ end
142
+ end
143
+ end
@@ -0,0 +1,3 @@
1
+ module Protector
2
+ VERSION = "0.0.1"
3
+ end
data/lib/protector.rb ADDED
@@ -0,0 +1,13 @@
1
+ require "active_support/all"
2
+
3
+ require "protector/version"
4
+ require "protector/dsl"
5
+ require "protector/adapters/active_record"
6
+
7
+ I18n.load_path << Dir[File.join File.expand_path(File.dirname(__FILE__)), '..', 'locales', '*.yml']
8
+
9
+ Protector::Adapters::ActiveRecord.activate! if defined?(ActiveRecord)
10
+
11
+ module Protector
12
+ # Your code goes here...
13
+ end
data/locales/en.yml ADDED
@@ -0,0 +1,3 @@
1
+ en:
2
+ protector:
3
+ invalid: "Access denied"
data/protector.gemspec ADDED
@@ -0,0 +1,23 @@
1
+ # coding: utf-8
2
+ lib = File.expand_path('../lib', __FILE__)
3
+ $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
4
+ require 'protector/version'
5
+
6
+ Gem::Specification.new do |spec|
7
+ spec.name = "protector"
8
+ spec.version = Protector::VERSION
9
+ spec.authors = ["Boris Staal"]
10
+ spec.email = ["boris@staal.io"]
11
+ spec.description = %q{Comfortable (seriously) white-list security restrictions for models on a field level}
12
+ spec.summary = %q{Protector is a successor to the Heimdallr gem: it hits the same goals keeping the Ruby way}
13
+ spec.homepage = "https://github.com/inossidabile/protector"
14
+ spec.license = "MIT"
15
+
16
+ spec.files = `git ls-files`.split($/)
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
18
+ spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
+ spec.require_paths = ["lib"]
20
+
21
+ spec.add_dependency "activesupport"
22
+ spec.add_dependency "i18n"
23
+ end
@@ -0,0 +1,96 @@
1
+ require 'spec_helpers/boot'
2
+
3
+ describe Protector::Adapters::ActiveRecord do
4
+ before(:all) do
5
+ ActiveRecord::Schema.verbose = false
6
+ ActiveRecord::Base.establish_connection adapter: "sqlite3", database: ":memory:"
7
+ ActiveRecord::Migration.create_table :dummies do |t|
8
+ t.string :string
9
+ t.integer :number
10
+ t.text :text
11
+ t.timestamps
12
+ end
13
+
14
+ class Dummy < ActiveRecord::Base; end
15
+ Dummy.create! string: 'zomgstring', number: 999, text: 'zomgtext'
16
+ Dummy.create! string: 'zomgstring', number: 777, text: 'zomgtext'
17
+
18
+ Protector::Adapters::ActiveRecord.activate!
19
+ end
20
+
21
+ describe Protector::Adapters::ActiveRecord::Base do
22
+ before(:each) do
23
+ @dummy = Class.new(ActiveRecord::Base) do
24
+ self.table_name = "dummies"
25
+ end
26
+ end
27
+
28
+ it "includes" do
29
+ @dummy.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
30
+ end
31
+
32
+ it "scopes" do
33
+ scope = @dummy.restrict('!')
34
+ scope.should be_an_instance_of ActiveRecord::Relation
35
+ scope.protector_subject.should == '!'
36
+ end
37
+
38
+ it_behaves_like "a model"
39
+ end
40
+
41
+ describe Protector::Adapters::ActiveRecord::Relation do
42
+ before(:all) do
43
+ @dummy = Class.new(ActiveRecord::Base) do
44
+ self.table_name = "dummies"
45
+ end
46
+ end
47
+
48
+ it "includes" do
49
+ @dummy.all.ancestors.should include(Protector::Adapters::ActiveRecord::Base)
50
+ end
51
+
52
+ it "saves subject" do
53
+ @dummy.all.restrict('!').where(number: 999).protector_subject.should == '!'
54
+ end
55
+
56
+ context "with null relation" do
57
+ before(:each) do
58
+ @dummy.instance_eval do
59
+ protect{ scope{ none } }
60
+ end
61
+ end
62
+
63
+ it "counts" do
64
+ @dummy.all.count.should == 2
65
+ @dummy.all.restrict('!').count.should == 0
66
+ end
67
+
68
+ it "fetches" do
69
+ fetched = @dummy.all.restrict('!').to_a
70
+
71
+ @dummy.all.to_a.length.should == 2
72
+ fetched.length.should == 0
73
+ end
74
+ end
75
+
76
+ context "with active relation" do
77
+ before(:each) do
78
+ @dummy.instance_eval do
79
+ protect{ scope{ where(number: 999) } }
80
+ end
81
+ end
82
+
83
+ it "counts" do
84
+ @dummy.all.count.should == 2
85
+ @dummy.all.restrict('!').count.should == 1
86
+ end
87
+
88
+ it "fetches" do
89
+ fetched = @dummy.all.restrict('!').to_a
90
+
91
+ @dummy.all.to_a.length.should == 2
92
+ fetched.length.should == 1
93
+ end
94
+ end
95
+ end
96
+ end
@@ -0,0 +1,116 @@
1
+ require 'spec_helpers/boot'
2
+
3
+ describe Protector::DSL do
4
+ describe Protector::DSL::Base do
5
+ before :each do
6
+ @base = Class.new{ include Protector::DSL::Base }
7
+ end
8
+
9
+ it "defines proper methods" do
10
+ @base.instance_methods.should include(:restrict)
11
+ @base.instance_methods.should include(:protector_subject)
12
+ end
13
+
14
+ it "remembers protection subject" do
15
+ base = @base.new
16
+ base.restrict("universe")
17
+ base.protector_subject.should == "universe"
18
+ end
19
+
20
+ it "forgets protection subject" do
21
+ base = @base.new
22
+ base.restrict("universe")
23
+ base.protector_subject.should == "universe"
24
+ base.unrestrict
25
+ base.protector_subject.should == nil
26
+ end
27
+ end
28
+
29
+ describe Protector::DSL::Entry do
30
+ before :each do
31
+ @entry = Class.new{ include Protector::DSL::Entry }
32
+ end
33
+
34
+ it "instantiates meta entity" do
35
+ @entry.instance_eval do
36
+ protect do; end
37
+ end
38
+
39
+ @entry.protector_meta.should be_an_instance_of(Protector::DSL::Meta)
40
+ end
41
+ end
42
+
43
+ describe Protector::DSL::Meta do
44
+ l = -> (x) { x > 4 }
45
+
46
+ before :each do
47
+ @meta = Protector::DSL::Meta.new
48
+
49
+ # << -> FTW!
50
+ @meta << -> {
51
+ scope { 'relation' }
52
+ }
53
+
54
+ @meta << -> (user) {
55
+ user.should == 'user'
56
+
57
+ can :view
58
+ cannot :view, %w(field5), :field4
59
+ }
60
+
61
+ @meta << -> (user, entry) {
62
+ user.should == 'user'
63
+ entry.should == 'entry'
64
+
65
+ can :update, %w(field1 field2 field3),
66
+ field4: 0..5,
67
+ field5: l
68
+
69
+ can :destroy
70
+ }
71
+ end
72
+
73
+ it "evaluates" do
74
+ @meta.evaluate(nil, [], 'user', 'entry')
75
+ end
76
+
77
+ it "sets relation" do
78
+ data = @meta.evaluate(nil, [], 'user', 'entry')
79
+ data.relation.should == 'relation'
80
+ end
81
+
82
+ it "sets access" do
83
+ data = @meta.evaluate(nil, %w(field1 field2 field3 field4 field5), 'user', 'entry')
84
+ data.access.should == {
85
+ "update" => {
86
+ "field1" => nil,
87
+ "field2" => nil,
88
+ "field3" => nil,
89
+ "field4" => 0..5,
90
+ "field5" => l
91
+ },
92
+ "view" => {
93
+ "field1" => nil,
94
+ "field2" => nil,
95
+ "field3" => nil
96
+ },
97
+ "create" => {}
98
+ }
99
+ end
100
+
101
+ it "marks destroyable" do
102
+ data = @meta.evaluate(nil, [], 'user', 'entry')
103
+ data.destroyable?.should == true
104
+ end
105
+
106
+ it "marks updatable" do
107
+ data = @meta.evaluate(nil, [], 'user', 'entry')
108
+ data.updatable?.should == true
109
+ end
110
+
111
+ it "marks creatable" do
112
+ data = @meta.evaluate(nil, [], 'user', 'entry')
113
+ data.creatable?.should == false
114
+ end
115
+ end
116
+ end
@@ -0,0 +1,18 @@
1
+ require 'pry'
2
+ require 'protector'
3
+
4
+ require 'active_record'
5
+
6
+ require_relative 'model'
7
+
8
+ RSpec.configure do |config|
9
+ config.treat_symbols_as_metadata_keys_with_true_values = true
10
+ config.run_all_when_everything_filtered = true
11
+ config.filter_run :focus
12
+
13
+ # Run specs in random order to surface order dependencies. If you find an
14
+ # order dependency and want to debug it, you can fix the order by providing
15
+ # the seed, which is printed after each run.
16
+ # --seed 1234
17
+ config.order = 'random'
18
+ end
@@ -0,0 +1,333 @@
1
+ RSpec::Matchers.define :invalidate do
2
+ match do |actual|
3
+ actual.save.should == false
4
+ actual.errors[:base].should == ["Access denied"]
5
+ end
6
+ end
7
+
8
+ RSpec::Matchers.define :validate do
9
+ match do |actual|
10
+ actual.class.transaction do
11
+ actual.save.should == true
12
+ raise ActiveRecord::Rollback
13
+ end
14
+
15
+ true
16
+ end
17
+ end
18
+
19
+ shared_examples_for "a model" do
20
+ it "evaluates meta properly" do
21
+ @dummy.instance_eval do
22
+ protect do |subject, dummy|
23
+ subject.should == '!'
24
+
25
+ scope { limit(5) }
26
+
27
+ can :view
28
+ can :create
29
+ can :update
30
+ end
31
+ end
32
+
33
+ fields = Hash[*%w(id string number text created_at updated_at).map{|x| [x, nil]}.flatten]
34
+ dummy = @dummy.new.restrict('!')
35
+ meta = dummy.protector_meta
36
+
37
+ meta.access[:view].should == fields
38
+ meta.access[:create].should == fields
39
+ meta.access[:update].should == fields
40
+ end
41
+
42
+ describe "visibility" do
43
+ it "marks blocked" do
44
+ @dummy.instance_eval do
45
+ protect do
46
+ scope { none }
47
+ end
48
+ end
49
+
50
+ @dummy.first.restrict('!').visible?.should == false
51
+ end
52
+
53
+ it "marks allowed" do
54
+ @dummy.instance_eval do
55
+ protect do
56
+ scope { limit(5) }
57
+ end
58
+ end
59
+
60
+ @dummy.first.restrict('!').visible?.should == true
61
+ end
62
+ end
63
+
64
+ describe "creatability" do
65
+ context "with empty meta" do
66
+ before(:each) do
67
+ @dummy.instance_eval do
68
+ protect do; end
69
+ end
70
+ end
71
+
72
+ it "marks blocked" do
73
+ dummy = @dummy.new(string: 'bam', number: 1)
74
+ dummy.restrict('!').creatable?.should == false
75
+ end
76
+
77
+ it "invalidates" do
78
+ dummy = @dummy.new(string: 'bam', number: 1).restrict('!')
79
+ dummy.should invalidate
80
+ end
81
+ end
82
+
83
+ context "by list of fields" do
84
+ before(:each) do
85
+ @dummy.instance_eval do
86
+ protect do
87
+ can :create, :string
88
+ end
89
+ end
90
+ end
91
+
92
+ it "marks blocked" do
93
+ dummy = @dummy.new(string: 'bam', number: 1)
94
+ dummy.restrict('!').creatable?.should == false
95
+ end
96
+
97
+ it "marks allowed" do
98
+ dummy = @dummy.new(string: 'bam')
99
+ dummy.restrict('!').creatable?.should == true
100
+ end
101
+
102
+ it "invalidates" do
103
+ dummy = @dummy.new(string: 'bam', number: 1).restrict('!')
104
+ dummy.should invalidate
105
+ end
106
+
107
+ it "validates" do
108
+ dummy = @dummy.new(string: 'bam').restrict('!')
109
+ dummy.should validate
110
+ end
111
+ end
112
+
113
+ context "by lambdas" do
114
+ before(:each) do
115
+ @dummy.instance_eval do
116
+ protect do
117
+ can :create, string: -> (x) { x.length == 5 }
118
+ end
119
+ end
120
+ end
121
+
122
+ it "marks blocked" do
123
+ dummy = @dummy.new(string: 'bam')
124
+ dummy.restrict('!').creatable?.should == false
125
+ end
126
+
127
+ it "marks allowed" do
128
+ dummy = @dummy.new(string: '12345')
129
+ dummy.restrict('!').creatable?.should == true
130
+ end
131
+
132
+ it "invalidates" do
133
+ dummy = @dummy.new(string: 'bam').restrict('!')
134
+ dummy.should invalidate
135
+ end
136
+
137
+ it "validates" do
138
+ dummy = @dummy.new(string: '12345').restrict('!')
139
+ dummy.should validate
140
+ end
141
+ end
142
+
143
+ context "by ranges" do
144
+ before(:each) do
145
+ @dummy.instance_eval do
146
+ protect do
147
+ can :create, number: 0..2
148
+ end
149
+ end
150
+ end
151
+
152
+ it "marks blocked" do
153
+ dummy = @dummy.new(number: 500)
154
+ dummy.restrict('!').creatable?.should == false
155
+ end
156
+
157
+ it "marks allowed" do
158
+ dummy = @dummy.new(number: 2)
159
+ dummy.restrict('!').creatable?.should == true
160
+ end
161
+
162
+ it "invalidates" do
163
+ dummy = @dummy.new(number: 500).restrict('!')
164
+ dummy.should invalidate
165
+ end
166
+
167
+ it "validates" do
168
+ dummy = @dummy.new(number: 2).restrict('!')
169
+ dummy.should validate
170
+ end
171
+ end
172
+ end
173
+
174
+ describe "updatability" do
175
+ context "with empty meta" do
176
+ before(:each) do
177
+ @dummy.instance_eval do
178
+ protect do; end
179
+ end
180
+ end
181
+
182
+ it "marks blocked" do
183
+ dummy = @dummy.first
184
+ dummy.assign_attributes(string: 'bam', number: 1)
185
+ dummy.restrict('!').updatable?.should == false
186
+ end
187
+
188
+ it "invalidates" do
189
+ dummy = @dummy.first.restrict('!')
190
+ dummy.assign_attributes(string: 'bam', number: 1)
191
+ dummy.should invalidate
192
+ end
193
+ end
194
+
195
+ context "by list of fields" do
196
+ before(:each) do
197
+ @dummy.instance_eval do
198
+ protect do
199
+ can :update, :string
200
+ end
201
+ end
202
+ end
203
+
204
+ it "marks blocked" do
205
+ dummy = @dummy.first
206
+ dummy.assign_attributes(string: 'bam', number: 1)
207
+ dummy.restrict('!').updatable?.should == false
208
+ end
209
+
210
+ it "marks allowed" do
211
+ dummy = @dummy.first
212
+ dummy.assign_attributes(string: 'bam')
213
+ dummy.restrict('!').updatable?.should == true
214
+ end
215
+
216
+ it "invalidates" do
217
+ dummy = @dummy.first.restrict('!')
218
+ dummy.assign_attributes(string: 'bam', number: 1)
219
+ dummy.should invalidate
220
+ end
221
+
222
+ it "validates" do
223
+ dummy = @dummy.first.restrict('!')
224
+ dummy.assign_attributes(string: 'bam')
225
+ dummy.should validate
226
+ end
227
+ end
228
+
229
+ context "by lambdas" do
230
+ before(:each) do
231
+ @dummy.instance_eval do
232
+ protect do
233
+ can :update, string: -> (x) { x.length == 5 }
234
+ end
235
+ end
236
+ end
237
+
238
+ it "marks blocked" do
239
+ dummy = @dummy.first
240
+ dummy.assign_attributes(string: 'bam')
241
+ dummy.restrict('!').updatable?.should == false
242
+ end
243
+
244
+ it "marks allowed" do
245
+ dummy = @dummy.first
246
+ dummy.assign_attributes(string: '12345')
247
+ dummy.restrict('!').updatable?.should == true
248
+ end
249
+
250
+ it "invalidates" do
251
+ dummy = @dummy.first.restrict('!')
252
+ dummy.assign_attributes(string: 'bam')
253
+ dummy.should invalidate
254
+ end
255
+
256
+ it "validates" do
257
+ dummy = @dummy.first.restrict('!')
258
+ dummy.assign_attributes(string: '12345')
259
+ dummy.should validate
260
+ end
261
+ end
262
+
263
+ context "by ranges" do
264
+ before(:each) do
265
+ @dummy.instance_eval do
266
+ protect do
267
+ can :update, number: 0..2
268
+ end
269
+ end
270
+ end
271
+
272
+ it "marks blocked" do
273
+ dummy = @dummy.first
274
+ dummy.assign_attributes(number: 500)
275
+ dummy.restrict('!').updatable?.should == false
276
+ end
277
+
278
+ it "marks allowed" do
279
+ dummy = @dummy.first
280
+ dummy.assign_attributes(number: 2)
281
+ dummy.restrict('!').updatable?.should == true
282
+ end
283
+
284
+ it "invalidates" do
285
+ dummy = @dummy.first.restrict('!')
286
+ dummy.assign_attributes(number: 500)
287
+ dummy.should invalidate
288
+ end
289
+
290
+ it "validates" do
291
+ dummy = @dummy.first.restrict('!')
292
+ dummy.assign_attributes(number: 2)
293
+ dummy.should validate
294
+ end
295
+ end
296
+ end
297
+
298
+ describe "destroyability" do
299
+ it "marks blocked" do
300
+ @dummy.instance_eval do
301
+ protect do; end
302
+ end
303
+
304
+ @dummy.first.restrict('!').destroyable?.should == false
305
+ end
306
+
307
+ it "marks allowed" do
308
+ @dummy.instance_eval do
309
+ protect do; can :destroy; end
310
+ end
311
+
312
+ @dummy.first.restrict('!').destroyable?.should == true
313
+ end
314
+
315
+ it "invalidates" do
316
+ @dummy.instance_eval do
317
+ protect do; end
318
+ end
319
+
320
+ @dummy.first.restrict('!').destroy.should == false
321
+ end
322
+
323
+ it "validates" do
324
+ @dummy.instance_eval do
325
+ protect do; can :destroy; end
326
+ end
327
+
328
+ dummy = @dummy.create!.restrict('!')
329
+ dummy.destroy.should == dummy
330
+ dummy.destroyed?.should == true
331
+ end
332
+ end
333
+ end
metadata ADDED
@@ -0,0 +1,94 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: protector
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ platform: ruby
6
+ authors:
7
+ - Boris Staal
8
+ autorequire:
9
+ bindir: bin
10
+ cert_chain: []
11
+ date: 2013-05-22 00:00:00.000000000 Z
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: activesupport
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - '>='
18
+ - !ruby/object:Gem::Version
19
+ version: '0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - '>='
25
+ - !ruby/object:Gem::Version
26
+ version: '0'
27
+ - !ruby/object:Gem::Dependency
28
+ name: i18n
29
+ requirement: !ruby/object:Gem::Requirement
30
+ requirements:
31
+ - - '>='
32
+ - !ruby/object:Gem::Version
33
+ version: '0'
34
+ type: :runtime
35
+ prerelease: false
36
+ version_requirements: !ruby/object:Gem::Requirement
37
+ requirements:
38
+ - - '>='
39
+ - !ruby/object:Gem::Version
40
+ version: '0'
41
+ description: Comfortable (seriously) white-list security restrictions for models on
42
+ a field level
43
+ email:
44
+ - boris@staal.io
45
+ executables: []
46
+ extensions: []
47
+ extra_rdoc_files: []
48
+ files:
49
+ - .gitignore
50
+ - .rspec
51
+ - Gemfile
52
+ - LICENSE.txt
53
+ - README.md
54
+ - Rakefile
55
+ - lib/protector.rb
56
+ - lib/protector/adapters/active_record.rb
57
+ - lib/protector/dsl.rb
58
+ - lib/protector/version.rb
59
+ - locales/en.yml
60
+ - protector.gemspec
61
+ - spec/lib/adapters/active_record_spec.rb
62
+ - spec/lib/dsl_spec.rb
63
+ - spec/spec_helpers/boot.rb
64
+ - spec/spec_helpers/model.rb
65
+ homepage: https://github.com/inossidabile/protector
66
+ licenses:
67
+ - MIT
68
+ metadata: {}
69
+ post_install_message:
70
+ rdoc_options: []
71
+ require_paths:
72
+ - lib
73
+ required_ruby_version: !ruby/object:Gem::Requirement
74
+ requirements:
75
+ - - '>='
76
+ - !ruby/object:Gem::Version
77
+ version: '0'
78
+ required_rubygems_version: !ruby/object:Gem::Requirement
79
+ requirements:
80
+ - - '>='
81
+ - !ruby/object:Gem::Version
82
+ version: '0'
83
+ requirements: []
84
+ rubyforge_project:
85
+ rubygems_version: 2.0.3
86
+ signing_key:
87
+ specification_version: 4
88
+ summary: 'Protector is a successor to the Heimdallr gem: it hits the same goals keeping
89
+ the Ruby way'
90
+ test_files:
91
+ - spec/lib/adapters/active_record_spec.rb
92
+ - spec/lib/dsl_spec.rb
93
+ - spec/spec_helpers/boot.rb
94
+ - spec/spec_helpers/model.rb