trusted_keys 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
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/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in trusted_keys.gemspec
4
+ gemspec
data/Guardfile ADDED
@@ -0,0 +1,8 @@
1
+ # A sample Guardfile
2
+ # More info at https://github.com/guard/guard#readme
3
+ #
4
+ guard 'minitest' do
5
+ watch(%r|^spec/(.*)_spec\.rb|)
6
+ watch(%r{^lib/(.*/)?([^/]+)\.rb$}) { |m| "spec/#{m[1]}#{m[2]}_spec.rb" }
7
+ watch(%r|^spec/minitest_helper\.rb|) { "spec" }
8
+ end
data/LICENSE ADDED
@@ -0,0 +1,22 @@
1
+ Copyright (c) 2012 Anders Törnqvist
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,35 @@
1
+ # TrustedKeys
2
+
3
+ TODO: Write a gem description
4
+
5
+ ## Installation
6
+
7
+ Add this line to your application's Gemfile:
8
+
9
+ gem 'trusted_keys'
10
+
11
+ And then execute:
12
+
13
+ $ bundle
14
+
15
+ Or install it yourself as:
16
+
17
+ $ gem install trusted_keys
18
+
19
+ ## Usage
20
+
21
+ TODO: Write usage instructions here
22
+
23
+ ## Other mass assignment protection gems for in controller
24
+ * https://github.com/topdan/param_accessible
25
+ * https://github.com/ryanb/trusted-params
26
+ * https://github.com/elabs/trusted_attributes
27
+ * https://github.com/rails/strong_parameters
28
+
29
+ ## Contributing
30
+
31
+ 1. Fork it
32
+ 2. Create your feature branch (`git checkout -b my-new-feature`)
33
+ 3. Commit your changes (`git commit -am 'Added some feature'`)
34
+ 4. Push to the branch (`git push origin my-new-feature`)
35
+ 5. Create new Pull Request
data/Rakefile ADDED
@@ -0,0 +1,18 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rake/testtask'
4
+
5
+ Rake::TestTask.new(:spec) do |t|
6
+ t.libs.push "lib"
7
+ t.libs.push "spec"
8
+ t.test_files = FileList['spec/**/*_spec.rb']
9
+ t.verbose = true
10
+ end
11
+
12
+ task :test => :spec
13
+ task :default => :test
14
+
15
+ desc "open console (require 'trusted_keys')"
16
+ task :c do
17
+ system "irb -I lib -r trusted_keys"
18
+ end
@@ -0,0 +1,120 @@
1
+ # encoding: utf-8
2
+ $LOAD_PATH << File.dirname(__FILE__) + "../../lib"
3
+ require 'trusted_keys'
4
+
5
+ # An exception is raised if untrusted keys are submitted when Rails.env
6
+ # equals 'development' or 'test'. When Rail.env is something else, it will
7
+ # silently remove the untrusted keys.
8
+ Rails.env = "development"
9
+ Rails.env = "test"
10
+
11
+ #Rails.env = "other"
12
+
13
+ module Trusted
14
+ extend ActiveSupport::Concern
15
+ include TrustedKeys
16
+ extend self
17
+
18
+ def params
19
+ { "utf8"=>"✓", "authenticity_token"=>"zIE/nwLd",
20
+ "event"=> { "title"=>"",
21
+ "location"=>"not trusted",
22
+ "start(1i)"=>"2012", "start(2i)"=>"3", "start(3i)"=>"5",
23
+ "description"=>"",
24
+ "attendees"=>"",
25
+ "slug_attributes" => { "slug" => "dddd",
26
+ "dangerous" => "not trusted" },
27
+ "comment_attributes" => { "body" => "I am body",
28
+ "evil" => "not trusted",
29
+ "slug3_attributes" => { "slug3" => "is cool",
30
+ "cruel" => "not trusted" } } } }
31
+ end
32
+
33
+ def puts
34
+ p trusted_attributes
35
+ end
36
+ end
37
+
38
+ puts "params => "
39
+ p Trusted.params
40
+
41
+ class ApplicationController
42
+ include Trusted
43
+ end
44
+
45
+ #ApplicationController.new.trusted_attributes # raises NoMethodError
46
+ #ApplicationController.new.puts # raises UsageError
47
+
48
+ class ApplicationController
49
+ trust :utf8
50
+ end
51
+ ApplicationController.new.puts # {"utf8"=>"✓" }
52
+
53
+ class ApplicationControllerX
54
+ include Trusted
55
+ trust :event, :utf8
56
+ end
57
+ Rails.env = "fff"
58
+ ApplicationControllerX.new.puts # {"utf8"=>"✓" "event" => "" }
59
+
60
+ # raises TrustedKeys::NotTrustedError if test och development environment.
61
+ #Rails.env = "test"
62
+ ApplicationControllerX.new.puts
63
+
64
+
65
+ #Rails.env = "test"
66
+ class ApplicationController2
67
+ include Trusted
68
+ trust :title, :start, :description, :attendees, :slug_attributes, for: :event
69
+ end
70
+ ApplicationController2.new.puts
71
+ # =>
72
+ # { "title"=>"", "start(1i)"=>"2012", "start(2i)"=>"3", "start(3i)"=>"5",
73
+ # "description"=>"", "attendees"=>"", "slug_attributes"=>""}
74
+
75
+ #Rails.env = "test"
76
+ class ApplicationController3
77
+ include Trusted
78
+ trust :title, :start, :description, :attendees, :slug_attributes,
79
+ :location, :comment_attributes, for: :event
80
+ trust :slug, for: "event.slug_attributes"
81
+ end
82
+ ApplicationController3.new.puts
83
+ # =>
84
+ # { "title"=>"", "start(1i)"=>"2012", "start(2i)"=>"3", "start(3i)"=>"5",
85
+ # "description"=>"", "attendees"=>"",
86
+ # "slug_attributes"=>{ "slug"=>"dddd"}}
87
+
88
+ #Rails.env = "development"
89
+ class ApplicationController4
90
+ include Trusted
91
+ trust :title, :start, :description, :attendees, :slug_attributes,
92
+ :comment_attributes, :location, for: :event
93
+ trust :slug, for: "event.slug_attributes"
94
+ trust "body", :slug3_attributes, for: "event.comment_attributes"
95
+ end
96
+ ApplicationController4.new.puts
97
+ # =>
98
+ # { "title"=>"", "start(1i)"=>"2012", "start(2i)"=>"3", "start(3i)"=>"5",
99
+ # "description"=>"", "attendees"=>"", "location" => "not trusted"
100
+ # "slug_attributes"=>{"slug"=>"dddd"},
101
+ # "comment_attributes"=>{ "body"=>"I am body",
102
+ # "slug3_attributes"=>""}}
103
+ #
104
+
105
+ #Rails.env = "test"
106
+ class ApplicationController5
107
+ include Trusted
108
+ trust :title, :start, :description, :attendees, :slug_attributes,
109
+ :comment_attributes, for: :event
110
+ trust :slug, for: "event.slug_attributes"
111
+ trust "body", :slug3_attributes, for: "event.comment_attributes"
112
+ trust "slug3", for: "event.comment_attributes.slug3_attributes"
113
+ end
114
+ ApplicationController5.new.puts
115
+ # =>
116
+ # { "title"=>"", "start(1i)"=>"2012", "start(2i)"=>"3", "start(3i)"=>"5",
117
+ # "description"=>"", "attendees"=>"",
118
+ # "slug_attributes"=>{"slug"=>"dddd"},
119
+ # "comment_attributes"=>{ "body"=>"I am body",
120
+ # "slug3_attributes"=>{"slug3"=>"is cool"}}}
@@ -0,0 +1,54 @@
1
+ require 'trusted_keys/version'
2
+ require 'rails'
3
+ require 'trusted_keys/trustable'
4
+ require 'trusted_keys/error/usage'
5
+ require 'trusted_keys/error/not_trusted'
6
+
7
+ module TrustedKeys
8
+ extend ActiveSupport::Concern
9
+
10
+ module ClassMethods
11
+ def trust(*args)
12
+ options = args.extract_options!
13
+ scope = options.fetch(:for, "").to_s.split '.'
14
+ env = options[:env] || Rails.env
15
+ nested = options.fetch(:nested, true)
16
+
17
+ klass = Class.new do
18
+ include Trustable
19
+ end
20
+
21
+ @_trusted_keys ||= []
22
+ @_trusted_keys << klass.new(:scope => scope,
23
+ :trusted_keys => @_trusted_keys,
24
+ :untrusted => Error::NotTrusted.new(env),
25
+ :nested => nested,
26
+ :keys => args)
27
+ end
28
+ end
29
+
30
+ private
31
+
32
+ def trusted_attributes
33
+ trusted_keys = self.class.instance_variable_get("@_trusted_keys")
34
+ raise Error::Usage.new(params) unless trusted_keys
35
+
36
+ sorted_keys = trusted_keys.sort
37
+
38
+ attributes = sorted_keys.first.attributes(params)
39
+
40
+ sorted_keys.drop(1).each do |trusted|
41
+ current_attributes = trusted.on_scope(attributes)
42
+
43
+ if trusted.parent_nested?(current_attributes)
44
+ current_attributes.each do |key, hash|
45
+ hash[trusted.key] = trusted.attributes(hash)
46
+ end
47
+ else
48
+ current_attributes[trusted.key] = trusted.attributes(current_attributes)
49
+ end
50
+ end
51
+
52
+ attributes
53
+ end
54
+ end
@@ -0,0 +1,40 @@
1
+ module TrustedKeys
2
+ module Error
3
+ class NotTrusted < StandardError
4
+ def initialize(env)
5
+ @keys = {}
6
+ @production = not(env.development? or env.test?)
7
+ end
8
+
9
+ def message
10
+ usage = @keys.map do |scope, keys|
11
+ "`trust #{keys}, for: '#{scope}'`"
12
+ end.join("\n")
13
+
14
+ "\n\nError: There are keys in the params hash that are not trusted. " +
15
+ "Set them as trusted with:\n#{usage} at the top of the controller."
16
+ end
17
+
18
+ def keys(options)
19
+ scope = options.fetch(:scope)
20
+ key = options.fetch(:key)
21
+ keys = options.fetch(:keys).flatten.map do |key|
22
+ ":#{key.to_s.sub(/\(\di\)/, '')}"
23
+ end.uniq.join(', ')
24
+
25
+ scope_key = (scope + [key]).compact.join('.')
26
+ @keys[scope_key] = keys unless scope_key.empty?
27
+
28
+ self
29
+ end
30
+
31
+ def present?
32
+ if @production
33
+ false
34
+ else
35
+ not @keys.empty?
36
+ end
37
+ end
38
+ end
39
+ end
40
+ end
@@ -0,0 +1,16 @@
1
+ module TrustedKeys
2
+ module Error
3
+ class Usage < StandardError
4
+ def initialize(params)
5
+ @params = params
6
+ end
7
+
8
+ def message
9
+ "\n\nparams => #{@params.inspect}\n\n" +
10
+ "Error: Before using `trusted_attributes` you must set the " +
11
+ "trusted keys in the controller, for examples: `trust :post` " +
12
+ "or `trust :title, :body, for: 'post'`"
13
+ end
14
+ end
15
+ end
16
+ end
@@ -0,0 +1,114 @@
1
+ require 'active_support/core_ext/hash/indifferent_access'
2
+
3
+ module TrustedKeys
4
+ module Trustable
5
+ extend ActiveSupport::Concern
6
+ include ActiveModel::MassAssignmentSecurity
7
+
8
+ def initialize(options)
9
+ @scope = options.fetch(:scope)
10
+ @trusted_keys = options.fetch(:trusted_keys)
11
+ @untrusted = options.fetch(:untrusted)
12
+ @nested = options.fetch(:nested, true)
13
+ keys = options.fetch(:keys)
14
+
15
+ if nested?({})
16
+ keys << "_destroy"
17
+ keys << "id"
18
+ end
19
+
20
+ self.class.send("attr_accessible", *keys)
21
+ end
22
+
23
+ def attributes(params)
24
+ params = params[key.to_sym] || params[key.to_s] if key
25
+
26
+ if nested?(params)
27
+ {}.tap do |hash|
28
+ params.each do |key, nested_hash|
29
+ hash[key] = sanitize(nested_hash)
30
+ end
31
+ end
32
+ elsif params.is_a?(Array)
33
+ [].tap do |array|
34
+ params.each do |hash|
35
+ array << sanitize(hash)
36
+ end
37
+ end
38
+ else
39
+ sanitize(params)
40
+ end
41
+ end
42
+
43
+ def on_scope(attributes)
44
+ @scope.slice(1, @scope.size - 2).reduce(attributes) do |attributes, key|
45
+ attributes[key]
46
+ end
47
+ end
48
+
49
+ def key; @key ||= @scope.last; end
50
+ def <=> (other); level <=> other.level; end
51
+ def level; @scope.size; end
52
+
53
+ def parent_nested?(params)
54
+ if level > 1
55
+ parent and parent.nested?(params)
56
+ else
57
+ false
58
+ end
59
+ end
60
+
61
+ protected
62
+
63
+ def nested?(params)
64
+ if params.is_a?(Hash)
65
+ @nested and key.to_s[/\A.+_attributes\Z/]
66
+ else
67
+ false
68
+ end
69
+ end
70
+
71
+ private
72
+
73
+ def parent
74
+ key = @scope.at(@scope.size - 2)
75
+
76
+ @trusted_keys.select do |trusted|
77
+ trusted.key.to_s == key and trusted.level == (level - 1)
78
+ end.first
79
+ end
80
+
81
+
82
+ def sanitize(params)
83
+ result = sanitize_for_mass_assignment(params)
84
+
85
+ keys = params.keys.map(&:to_s) - result.keys.map(&:to_s)
86
+
87
+ unless keys.empty?
88
+ untrusted = @untrusted.keys(:scope => @scope,
89
+ :key => nil,
90
+ :keys => keys)
91
+ raise untrusted if untrusted.present?
92
+ end
93
+
94
+ remove_untrusted_keys(result)
95
+ end
96
+
97
+ def remove_untrusted_keys(attributes)
98
+ trusted_keys = @trusted_keys.select do |trusted|
99
+ trusted.level == (level + 1)
100
+ end.map { |trusted| trusted.key.to_s }
101
+
102
+ attributes.each do |key, value|
103
+ if value.is_a?(Hash) and !trusted_keys.include?(key.to_s)
104
+ attributes[key] = ""
105
+ @untrusted.keys :scope => @scope, :key => key, :keys => value.keys
106
+ end
107
+ end
108
+
109
+ raise @untrusted if @untrusted.present?
110
+
111
+ HashWithIndifferentAccess.new(attributes)
112
+ end
113
+ end
114
+ end
@@ -0,0 +1,3 @@
1
+ module TrustedKeys
2
+ VERSION = "0.0.1"
3
+ end
@@ -0,0 +1,8 @@
1
+ require 'minitest/autorun'
2
+ require 'minitest-colorize'
3
+ require "trusted_keys"
4
+ require 'ostruct'
5
+
6
+ Kernel.instance_eval do
7
+ alias_method :context, :describe
8
+ end
@@ -0,0 +1,274 @@
1
+ require 'minitest_helper'
2
+
3
+ NotTrusted = TrustedKeys::Error::NotTrusted
4
+ Usage = TrustedKeys::Error::Usage
5
+
6
+ describe TrustedKeys::Trustable do
7
+ let(:test) { OpenStruct.new(:test? => true, :development? => false) }
8
+
9
+ let(:klass) do
10
+ Class.new do
11
+ include TrustedKeys::Trustable
12
+ end
13
+ end
14
+
15
+ let(:params) do
16
+ { "email" => "anders@email.com",
17
+ :controller => "events",
18
+ :collection => [ { "one" => "ok 1",
19
+ "two" => "remove me"},
20
+ { "one" => "ok 2",
21
+ "two" => "remove me 2"} ],
22
+ :time => { "start_time(1i)"=>"2012",
23
+ "start_time(2i)"=>"3",
24
+ "start_time(3i)"=>"14" },
25
+ :events => {
26
+ :nested_attributes => { "0" => { "_destroy"=>"false",
27
+ "dangerous" => "remove me",
28
+ "start"=>"2012" },
29
+ "new_1331711737056" => { "_destroy"=>"false",
30
+ "start"=>"2012" } } },
31
+ :password => "secret",
32
+ :post => { :body => "I am a body",
33
+ :title => "This is my title",
34
+ :comments => { 'body' => 'My body',
35
+ :email => "an email",
36
+ :author => { :name => "anders" } } } }
37
+ end
38
+
39
+ def options(options_hash)
40
+ { :scope => [],
41
+ :trusted_keys => [],
42
+ :untrusted => NotTrusted.new(OpenStruct.new(:test? => false,
43
+ :development? => false)),
44
+ :keys => [:email]
45
+ }.merge(options_hash)
46
+ end
47
+
48
+ describe "attributes inside a collection" do
49
+ it "returns trusted attributes" do
50
+ t = klass.new(options(:scope => [:collection],
51
+ :keys => ["one"]))
52
+ expected = [{ "one" => "ok 1" }, { "one" => "ok 2" }]
53
+
54
+ t.attributes(params).must_equal(expected)
55
+ end
56
+ end
57
+
58
+ describe "datetime selects" do
59
+ it "return all datetime attributes" do
60
+ t = klass.new(options(:scope => [:time],
61
+ :trusted_keys => [],
62
+ :keys => [:start_time]))
63
+
64
+ expected = { "start_time(1i)"=>"2012",
65
+ "start_time(2i)"=>"3",
66
+ "start_time(3i)"=>"14" }
67
+ t.attributes(params).must_equal(expected)
68
+ end
69
+ end
70
+
71
+ describe "nested attributes" do
72
+ it "returns nested hash" do
73
+ t = klass.new(options(:scope => [:events, :nested_attributes],
74
+ :trusted_keys => [],
75
+ :keys => [ "start" ]))
76
+
77
+ expected = { "0" => { "_destroy"=>"false",
78
+ "start"=>"2012" },
79
+ "new_1331711737056" => { "_destroy"=>"false",
80
+ "start"=>"2012" } }
81
+ t.attributes(params[:events]).must_equal(expected)
82
+ end
83
+ end
84
+
85
+ describe "#attributes" do
86
+ describe "when in test or development environment" do
87
+ context "level 0" do
88
+ it "doesn't raise an exception if all params isn't trusted" do
89
+ t = klass.new(options(:scope => [],
90
+ :trusted_keys => [],
91
+ :untrusted => NotTrusted.new(test),
92
+ :keys => [:email]))
93
+ t.attributes(params).must_equal("email" => "anders@email.com")
94
+ end
95
+ end
96
+
97
+ context "level 1" do
98
+ it "raises an exception if all params isn't trusted" do
99
+ t = klass.new(options(:scope => [:post],
100
+ :trusted_keys => [],
101
+ :untrusted => NotTrusted.new(test),
102
+ :keys => [:body]))
103
+ proc { t.attributes(params) }.must_raise NotTrusted
104
+ end
105
+ end
106
+
107
+ context "level 2" do
108
+ it "raises an exception if all params isn't trusted" do
109
+ t = klass.new(options(:scope => [:post, :comments],
110
+ :trusted_keys => [],
111
+ :untrusted => NotTrusted.new(test),
112
+ :keys => [:body]))
113
+ proc { t.attributes(params[:post]) }.must_raise NotTrusted
114
+ end
115
+ end
116
+
117
+ context "hash on next level isn't trusted" do
118
+ it "raises an exception" do
119
+ t = klass.new(options(:scope => [],
120
+ :trusted_keys => [],
121
+ :untrusted => NotTrusted.new(test),
122
+ :keys => [:email, :password, :post]))
123
+ proc { t.attributes(params) }.must_raise NotTrusted
124
+ end
125
+ end
126
+ end
127
+
128
+ context "level 0" do
129
+ it "returns trusted params for level 0" do
130
+ t = klass.new(options(:scope => [],
131
+ :trusted_keys => [],
132
+ :keys => [:email]))
133
+ t.attributes(params).must_equal("email" => 'anders@email.com')
134
+ end
135
+
136
+ it %(transform hash on next level to an empty string if its keys
137
+ aren't trusted) do
138
+ t = klass.new(options(:scope => [],
139
+ :trusted_keys => [],
140
+ :keys => [:post]))
141
+ t.attributes(params).must_equal("post" => '')
142
+ end
143
+
144
+ it "returns the hash on next level if it has trusted keys" do
145
+ level1 = klass.new(options(:scope => [:post],
146
+ :trusted_keys => [],
147
+ :keys => [:body]))
148
+ t = klass.new(options(:scope => [],
149
+ :trusted_keys => [level1],
150
+ :keys => [:post]))
151
+
152
+ expected = {"post"=> { "body"=>"I am a body",
153
+ "title"=>"This is my title",
154
+ "comments"=> { "body"=>"My body",
155
+ "email"=>"an email",
156
+ "author"=>{"name"=>"anders"}} } }
157
+
158
+ t.attributes(params).must_equal(expected)
159
+ level1.attributes(params).must_equal("body" => "I am a body")
160
+ end
161
+ end
162
+
163
+ context "level 1" do
164
+ it "returns trusted params for level 1" do
165
+ t = klass.new(options(:scope => [:post],
166
+ :trusted_keys => [],
167
+ :keys => [:body]))
168
+ t.attributes(params).must_equal("body" => 'I am a body')
169
+ end
170
+ end
171
+
172
+ context "level 2" do
173
+ it "returns trusted params for level 2" do
174
+ t = klass.new(options(:scope => [:post, :comments],
175
+ :trusted_keys => [],
176
+ :keys => [:body]))
177
+ t.attributes(params[:post]).must_equal("body" => 'My body')
178
+ end
179
+ end
180
+ end
181
+
182
+ describe "#on_scope" do
183
+ context "level 2" do
184
+ it "returns the hash for that level" do
185
+ t = klass.new(options(:scope => [:post, :comments],
186
+ :trusted_keys => [],
187
+ :keys => [:body]))
188
+ t.on_scope(params[:post]).must_equal(params[:post])
189
+ end
190
+ end
191
+
192
+ context "level 3" do
193
+ it "returns the hash for that level" do
194
+ t = klass.new(options(:scope => [:post, :comments, :author],
195
+ :trusted_keys => [],
196
+ :keys => [:name]))
197
+ t.on_scope(params[:post]).must_equal(params[:post][:comments])
198
+ end
199
+ end
200
+
201
+ context "level 1" do
202
+ it "not applicable" do
203
+ t = klass.new(options(:scope => [:post],
204
+ :trusted_keys => [],
205
+ :keys => [:body]))
206
+ proc { t.on_scope(params) }.must_raise NoMethodError
207
+ end
208
+ end
209
+
210
+ context "level 0" do
211
+ it "not applicable" do
212
+ t = klass.new(options(:scope => [],
213
+ :trusted_keys => [],
214
+ :keys => [:body]))
215
+ proc { t.on_scope(params) }.must_raise NoMethodError
216
+ end
217
+ end
218
+ end
219
+
220
+ describe "#key" do
221
+ context "scope is empty" do
222
+ it "returns nil" do
223
+ klass.new(options(:scope => [])).key.must_equal nil
224
+ end
225
+ end
226
+
227
+ context "scope has 1 key" do
228
+ it "returns the key" do
229
+ klass.new(options(:scope => [:post])).key.must_equal :post
230
+ end
231
+ end
232
+
233
+ context "scope has 2 keys" do
234
+ it "returns the last key - deepest key" do
235
+ klass.new(options(:scope => [:post, :comment])).key.must_equal :comment
236
+ end
237
+ end
238
+ end
239
+
240
+ describe "#level" do
241
+ context "scope is empty" do
242
+ it "returns 0" do
243
+ klass.new(options(:scope => [])).level.must_equal 0
244
+ end
245
+ end
246
+
247
+ context "scope has 1 key" do
248
+ it "returns 1" do
249
+ klass.new(options(:scope =>[:post])).level.must_equal 1
250
+ end
251
+ end
252
+
253
+ context "scope has 2 keys" do
254
+ it "returns 2" do
255
+ klass.new(options(:scope => [:post, :comment])).level.must_equal 2
256
+ end
257
+ end
258
+ end
259
+
260
+ describe "#<=>" do
261
+ describe "sort an array" do
262
+ before do
263
+ @a1 = klass.new(options(:scope =>[:post, :comment]))
264
+ @a2 = klass.new(options(:scope => [:post]))
265
+ @array = [@a1, @a2]
266
+ @array.first.must_equal @a1
267
+ end
268
+
269
+ it "sort objects with the lowest level first" do
270
+ @array.sort.first.must_equal @a2
271
+ end
272
+ end
273
+ end
274
+ end
@@ -0,0 +1,186 @@
1
+ require 'minitest_helper'
2
+
3
+ describe TrustedKeys do
4
+ let(:controller) do
5
+ Class.new do
6
+ include TrustedKeys
7
+
8
+ def params
9
+ { "email" => "anders@email.com",
10
+ :time => { "start_time(1i)"=>"2012",
11
+ "start_time(2i)"=>"3",
12
+ "start_time(3i)"=>"14" },
13
+ :event => {
14
+ :password => "secret",
15
+ :collection_attributes => [ { "one" => "ok 1",
16
+ "two" => "remove me"},
17
+ { "one" => "ok 2",
18
+ "two" => "remove me 2"} ],
19
+ :nested_attributes => {
20
+ "0" => { "_destroy"=>"false",
21
+ "start"=>"2012" },
22
+ "new_1331711737056" => { "_destroy"=>"false",
23
+ "start"=>"2012" } } },
24
+ "survey"=> {
25
+ "name"=>"survery 1",
26
+ "questions_attributes"=>
27
+ { "0"=>{"_destroy"=>"1",
28
+ "content"=>"question2",
29
+ "dddd" => "xxxx",
30
+ "id" => "1",
31
+ "answers_attributes"=>{
32
+ "0"=>{"content"=>"answer1","_destroy"=>"","dd" => "x"},
33
+ "1"=>{"content"=>"answer 2","_destroy"=>"1","id"=>"2"}
34
+ }},
35
+ "1"=>{"_destroy"=>"1",
36
+ "content"=>"",
37
+ "answers_attributes"=>{
38
+ "1"=>{"content"=>"", "_destroy"=>""}}}}},
39
+
40
+ :controller => "events",
41
+ :password => "secret",
42
+ :post => { :body => "I am a body",
43
+ :title => "This is my title",
44
+ :comments => { 'body' => 'My body',
45
+ :email => "an email",
46
+ :author => { :name => "anders" } } } }
47
+ end
48
+ end
49
+ end
50
+
51
+ let(:env) { OpenStruct.new(:test? => false, :development? => false) }
52
+
53
+ describe ".trust" do
54
+ describe "datetime selects" do
55
+ it "return all datetime attributes" do
56
+ controller.trust :start_time, :for => :time, :env => env
57
+ trusted_attributes = controller.new.send(:trusted_attributes)
58
+
59
+ expected = { "start_time(1i)"=>"2012",
60
+ "start_time(2i)"=>"3",
61
+ "start_time(3i)"=>"14" }
62
+ trusted_attributes.must_equal(expected)
63
+ end
64
+ end
65
+
66
+ describe "hashes inside a collection" do
67
+ it "returns trusted attributes" do
68
+ controller.trust :collection_attributes, "password", :for => :event,
69
+ :env => env
70
+ controller.trust :one, :for => 'event.collection_attributes',
71
+ :env => env
72
+ trusted_attributes = controller.new.send(:trusted_attributes)
73
+
74
+ expected = { "password" => "secret",
75
+ "collection_attributes" => [{ "one" => "ok 1" },
76
+ { "one" => "ok 2" }] }
77
+ trusted_attributes.must_equal(expected)
78
+ end
79
+ end
80
+
81
+ context "nested attributes" do
82
+ it "returns nested hash" do
83
+ controller.trust :questions_attributes, :name, :for => :survey,
84
+ :env => env
85
+ controller.trust "answers_attributes", "content",
86
+ :for => 'survey.questions_attributes', :env => env
87
+
88
+ controller.trust "content",
89
+ :for => 'survey.questions_attributes.answers_attributes', :env => env
90
+
91
+ trusted_attributes = controller.new.send(:trusted_attributes)
92
+
93
+ expected = {
94
+ "name"=>"survery 1",
95
+ "questions_attributes"=>
96
+ { "0"=>{"_destroy"=>"1",
97
+ "content"=>"question2",
98
+ "id" => "1",
99
+ "answers_attributes"=>{
100
+ "0"=>{"content"=>"answer1", "_destroy"=>""},
101
+ "1"=>{"content"=>"answer 2", "_destroy"=>"1", "id"=>"2"}}},
102
+ "1"=>{"_destroy"=>"1",
103
+ "content"=>"",
104
+ "answers_attributes"=>{
105
+ "1"=>{"content"=>"", "_destroy"=>""}}}}}
106
+
107
+ trusted_attributes.must_equal(expected)
108
+ end
109
+
110
+ it "returns nested hash" do
111
+ controller.trust :nested_attributes, :for => :event, :env => env
112
+ controller.trust "start", :for => 'event.nested_attributes', :env => env
113
+
114
+ trusted_attributes = controller.new.send(:trusted_attributes)
115
+
116
+ expected = { "nested_attributes" => {
117
+ "0" => { "_destroy"=>"false",
118
+ "start"=>"2012" },
119
+ "new_1331711737056" => { "_destroy"=>"false",
120
+ "start"=>"2012" } } }
121
+ trusted_attributes.must_equal(expected)
122
+ end
123
+ end
124
+
125
+ context "no trusted keys" do
126
+ it "raises an exception" do
127
+ proc {
128
+ controller.new.send(:trusted_attributes)
129
+ }.must_raise TrustedKeys::Error::Usage
130
+ end
131
+ end
132
+
133
+ context "0 level" do
134
+ it "returns trusted keys" do
135
+ controller.trust :email , :env => env
136
+ trusted_attributes = controller.new.send(:trusted_attributes)
137
+ trusted_attributes.must_equal("email" => "anders@email.com")
138
+ end
139
+ end
140
+
141
+ context "1 level" do
142
+ it "returns trusted keys" do
143
+ controller.trust :body, :for => :post, :env => env
144
+ trusted_attributes = controller.new.send(:trusted_attributes)
145
+
146
+ expected = { "body" => "I am a body" }
147
+ trusted_attributes.must_equal(expected)
148
+ end
149
+
150
+ it "returns trusted keys" do
151
+ controller.trust :body, :title, :comments, :for => :post, :env => env
152
+ trusted_attributes = controller.new.send(:trusted_attributes)
153
+
154
+ expected = { "body" => "I am a body",
155
+ "title" => "This is my title",
156
+ "comments" => "" }
157
+ trusted_attributes.must_equal(expected)
158
+ end
159
+ end
160
+
161
+ context "2 levels" do
162
+ it "returns trusted keys" do
163
+ controller.trust :body, :comments, :for => :post, :env => env
164
+ controller.trust :author, :for => 'post.comments', :env => env
165
+ trusted_attributes = controller.new.send(:trusted_attributes)
166
+
167
+ expected = { "body" => "I am a body",
168
+ "comments" => { "author" => "" } }
169
+ trusted_attributes.must_equal(expected)
170
+ end
171
+ end
172
+
173
+ context "3 levels" do
174
+ it "returns trusted keys" do
175
+ controller.trust :body, :comments, :for => :post, :env => env
176
+ controller.trust :author, :for => 'post.comments', :env => env
177
+ controller.trust :name, :for => 'post.comments.author', :env => env
178
+ trusted_attributes = controller.new.send(:trusted_attributes)
179
+
180
+ expected = { "body" => "I am a body",
181
+ "comments" => { "author" => { "name" => "anders" } } }
182
+ trusted_attributes.must_equal(expected)
183
+ end
184
+ end
185
+ end
186
+ end
@@ -0,0 +1,25 @@
1
+ # -*- encoding: utf-8 -*-
2
+ require File.expand_path('../lib/trusted_keys/version', __FILE__)
3
+
4
+ Gem::Specification.new do |gem|
5
+ gem.authors = ["Anders Törnqvist"]
6
+ gem.email = ["anders.tornqvist@gmail.com"]
7
+ gem.description = %q{Mass assignment security in your controller}
8
+ gem.summary = %q{Mass assignment security in your controller}
9
+ gem.homepage = ""
10
+
11
+ gem.executables = `git ls-files -- bin/*`.split("\n").map do |f|
12
+ File.basename(f)
13
+ end
14
+ gem.files = `git ls-files`.split("\n")
15
+ gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
16
+ gem.name = "trusted_keys"
17
+ gem.require_paths = ["lib"]
18
+ gem.version = TrustedKeys::VERSION
19
+
20
+ gem.add_runtime_dependency "rails", ["~> 3.0"]
21
+ gem.add_development_dependency "minitest", ["~> 2.11"]
22
+ gem.add_development_dependency "guard", ["~> 1.0"]
23
+ gem.add_development_dependency "guard-minitest", ["~> 0.5"]
24
+ gem.add_development_dependency "minitest-colorize", ["~> 0.0.4"]
25
+ end
metadata ADDED
@@ -0,0 +1,144 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: trusted_keys
3
+ version: !ruby/object:Gem::Version
4
+ version: 0.0.1
5
+ prerelease:
6
+ platform: ruby
7
+ authors:
8
+ - Anders Törnqvist
9
+ autorequire:
10
+ bindir: bin
11
+ cert_chain: []
12
+ date: 2012-03-30 00:00:00.000000000 Z
13
+ dependencies:
14
+ - !ruby/object:Gem::Dependency
15
+ name: rails
16
+ requirement: !ruby/object:Gem::Requirement
17
+ none: false
18
+ requirements:
19
+ - - ~>
20
+ - !ruby/object:Gem::Version
21
+ version: '3.0'
22
+ type: :runtime
23
+ prerelease: false
24
+ version_requirements: !ruby/object:Gem::Requirement
25
+ none: false
26
+ requirements:
27
+ - - ~>
28
+ - !ruby/object:Gem::Version
29
+ version: '3.0'
30
+ - !ruby/object:Gem::Dependency
31
+ name: minitest
32
+ requirement: !ruby/object:Gem::Requirement
33
+ none: false
34
+ requirements:
35
+ - - ~>
36
+ - !ruby/object:Gem::Version
37
+ version: '2.11'
38
+ type: :development
39
+ prerelease: false
40
+ version_requirements: !ruby/object:Gem::Requirement
41
+ none: false
42
+ requirements:
43
+ - - ~>
44
+ - !ruby/object:Gem::Version
45
+ version: '2.11'
46
+ - !ruby/object:Gem::Dependency
47
+ name: guard
48
+ requirement: !ruby/object:Gem::Requirement
49
+ none: false
50
+ requirements:
51
+ - - ~>
52
+ - !ruby/object:Gem::Version
53
+ version: '1.0'
54
+ type: :development
55
+ prerelease: false
56
+ version_requirements: !ruby/object:Gem::Requirement
57
+ none: false
58
+ requirements:
59
+ - - ~>
60
+ - !ruby/object:Gem::Version
61
+ version: '1.0'
62
+ - !ruby/object:Gem::Dependency
63
+ name: guard-minitest
64
+ requirement: !ruby/object:Gem::Requirement
65
+ none: false
66
+ requirements:
67
+ - - ~>
68
+ - !ruby/object:Gem::Version
69
+ version: '0.5'
70
+ type: :development
71
+ prerelease: false
72
+ version_requirements: !ruby/object:Gem::Requirement
73
+ none: false
74
+ requirements:
75
+ - - ~>
76
+ - !ruby/object:Gem::Version
77
+ version: '0.5'
78
+ - !ruby/object:Gem::Dependency
79
+ name: minitest-colorize
80
+ requirement: !ruby/object:Gem::Requirement
81
+ none: false
82
+ requirements:
83
+ - - ~>
84
+ - !ruby/object:Gem::Version
85
+ version: 0.0.4
86
+ type: :development
87
+ prerelease: false
88
+ version_requirements: !ruby/object:Gem::Requirement
89
+ none: false
90
+ requirements:
91
+ - - ~>
92
+ - !ruby/object:Gem::Version
93
+ version: 0.0.4
94
+ description: Mass assignment security in your controller
95
+ email:
96
+ - anders.tornqvist@gmail.com
97
+ executables: []
98
+ extensions: []
99
+ extra_rdoc_files: []
100
+ files:
101
+ - .gitignore
102
+ - Gemfile
103
+ - Guardfile
104
+ - LICENSE
105
+ - README.md
106
+ - Rakefile
107
+ - example/controller.rb
108
+ - lib/trusted_keys.rb
109
+ - lib/trusted_keys/error/not_trusted.rb
110
+ - lib/trusted_keys/error/usage.rb
111
+ - lib/trusted_keys/trustable.rb
112
+ - lib/trusted_keys/version.rb
113
+ - spec/minitest_helper.rb
114
+ - spec/trusted_keys/trustable_spec.rb
115
+ - spec/trusted_keys_spec.rb
116
+ - trusted_keys.gemspec
117
+ homepage: ''
118
+ licenses: []
119
+ post_install_message:
120
+ rdoc_options: []
121
+ require_paths:
122
+ - lib
123
+ required_ruby_version: !ruby/object:Gem::Requirement
124
+ none: false
125
+ requirements:
126
+ - - ! '>='
127
+ - !ruby/object:Gem::Version
128
+ version: '0'
129
+ required_rubygems_version: !ruby/object:Gem::Requirement
130
+ none: false
131
+ requirements:
132
+ - - ! '>='
133
+ - !ruby/object:Gem::Version
134
+ version: '0'
135
+ requirements: []
136
+ rubyforge_project:
137
+ rubygems_version: 1.8.21
138
+ signing_key:
139
+ specification_version: 3
140
+ summary: Mass assignment security in your controller
141
+ test_files:
142
+ - spec/minitest_helper.rb
143
+ - spec/trusted_keys/trustable_spec.rb
144
+ - spec/trusted_keys_spec.rb