tainted_hash 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
data/LICENSE.md ADDED
@@ -0,0 +1,20 @@
1
+ Copyright (c) 2012 Rick Olson
2
+
3
+ Permission is hereby granted, free of charge, to any person obtaining
4
+ a copy of this software and associated documentation files (the
5
+ "Software"), to deal in the Software without restriction, including
6
+ without limitation the rights to use, copy, modify, merge, publish,
7
+ distribute, sublicense, and/or sell copies of the Software, and to
8
+ permit persons to whom the Software is furnished to do so, subject to
9
+ the following conditions:
10
+
11
+ The above copyright notice and this permission notice shall be
12
+ included in all copies or substantial portions of the Software.
13
+
14
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
15
+ EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
16
+ MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
17
+ NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
18
+ LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
19
+ OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
20
+ WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
data/README.md ADDED
@@ -0,0 +1,52 @@
1
+ # Tainted Hash
2
+
3
+ A TaintedHash is a wrapper around a normal Hash that only exposes the keys that
4
+ have been approved. This is useful in cases where a Hash is built from user
5
+ input from an external service (such as Rails or Sinatra). By forcing the
6
+ developer to approve keys, no unexpected keys are passed to data stores.
7
+ Because of this specific use case, it is assumed all keys are strings.
8
+
9
+ By default, no keys have been approved.
10
+
11
+ ```ruby
12
+ hash = {'a' => 1, 'b' => 2, 'c' => 3}
13
+ tainted = TaintedHash.new hash
14
+ ```
15
+
16
+ You can access keys manually to get the value and approve them:
17
+
18
+ Use `#expose` to expose keys.
19
+
20
+ ```ruby
21
+ tainted.include?(:a) # false
22
+ tainted['a'] # Returns 1
23
+ tainted[:a] # Symbols are OK too.
24
+ tainted.include?(:a) # false, not exposed
25
+ tainted.expose :a
26
+ tainted.include?(:a) # true
27
+ tainted.keys # ['a']
28
+ ```
29
+
30
+ If using Rails 2.3, require `tainted_hash/rails` to setup the necessary hooks.
31
+ It amounts to little more than this:
32
+
33
+ ```ruby
34
+ def wrap_params_with_tainted_hash
35
+ @_params = TaintedHash.new(@_params.to_hash)
36
+ end
37
+ ```
38
+
39
+ Set this up as a `before_filter` early in the stack. However, it should run
40
+ after filters like `#filter_parameter_logging` that needs to filter _any_
41
+ key.
42
+
43
+ ## Note on Patches/Pull Requests
44
+ 1. Fork the project on GitHub.
45
+ 2. Make your feature addition or bug fix.
46
+ 3. Add tests for it. This is important so I don't break it in a future version
47
+ unintentionally.
48
+ 4. Commit, do not mess with rakefile, version, or history. (if you want to have
49
+ your own version, that is fine but bump version in a commit by itself I can
50
+ ignore when I pull)
51
+ 5. Send me a pull request. Bonus points for topic branches.
52
+
data/Rakefile ADDED
@@ -0,0 +1,134 @@
1
+ #!/usr/bin/env rake
2
+
3
+ require 'date'
4
+
5
+ #############################################################################
6
+ #
7
+ # Helper functions
8
+ #
9
+ #############################################################################
10
+
11
+ def name
12
+ @name ||= Dir['*.gemspec'].first.split('.').first
13
+ end
14
+
15
+ def version
16
+ line = File.read("lib/#{name}.rb")[/^\s*VERSION\s*=\s*.*/]
17
+ line.match(/.*VERSION\s*=\s*['"](.*)['"]/)[1]
18
+ end
19
+
20
+ def date
21
+ Date.today.to_s
22
+ end
23
+
24
+ def rubyforge_project
25
+ name
26
+ end
27
+
28
+ def gemspec_file
29
+ "#{name}.gemspec"
30
+ end
31
+
32
+ def gem_file
33
+ "#{name}-#{version}.gem"
34
+ end
35
+
36
+ def replace_header(head, header_name)
37
+ head.sub!(/(\.#{header_name}\s*= ').*'/) { "#{$1}#{send(header_name)}'"}
38
+ end
39
+
40
+ #############################################################################
41
+ #
42
+ # Standard tasks
43
+ #
44
+ #############################################################################
45
+
46
+ task :default => :test
47
+
48
+ require 'rake/testtask'
49
+ Rake::TestTask.new(:test) do |test|
50
+ test.libs << 'lib' << 'test'
51
+ test.pattern = 'test/**/*_test.rb'
52
+ test.verbose = true
53
+ end
54
+
55
+ desc "Open an irb session preloaded with this library"
56
+ task :console do
57
+ sh "irb -rubygems -r ./lib/#{name}.rb"
58
+ end
59
+
60
+ #############################################################################
61
+ #
62
+ # Custom tasks (add your own tasks here)
63
+ #
64
+ #############################################################################
65
+
66
+
67
+
68
+ #############################################################################
69
+ #
70
+ # Packaging tasks
71
+ #
72
+ #############################################################################
73
+
74
+ desc "Create tag v#{version} and build and push #{gem_file} to Rubygems"
75
+ task :release => :build do
76
+ unless `git branch` =~ /^\* master$/
77
+ puts "You must be on the master branch to release!"
78
+ exit!
79
+ end
80
+ sh "git commit --allow-empty -a -m 'Release #{version}'"
81
+ sh "git tag v#{version}"
82
+ sh "git push origin master"
83
+ sh "git push origin v#{version}"
84
+ sh "gem push pkg/#{gem_file}"
85
+ end
86
+
87
+ desc "Build #{gem_file} into the pkg directory"
88
+ task :build => :gemspec do
89
+ sh "mkdir -p pkg"
90
+ sh "gem build #{gemspec_file}"
91
+ sh "mv #{gem_file} pkg"
92
+ end
93
+
94
+ desc "Generate #{gemspec_file}"
95
+ task :gemspec => :validate do
96
+ # read spec file and split out manifest section
97
+ spec = File.read(gemspec_file)
98
+ head, manifest, tail = spec.split(" # = MANIFEST =\n")
99
+
100
+ # replace name version and date
101
+ replace_header(head, :name)
102
+ replace_header(head, :version)
103
+ replace_header(head, :date)
104
+ #comment this out if your rubyforge_project has a different name
105
+ replace_header(head, :rubyforge_project)
106
+
107
+ # determine file list from git ls-files
108
+ files = `git ls-files`.
109
+ split("\n").
110
+ sort.
111
+ reject { |file| file =~ /^\./ }.
112
+ reject { |file| file =~ /^(rdoc|pkg)/ }.
113
+ map { |file| " #{file}" }.
114
+ join("\n")
115
+
116
+ # piece file back together and write
117
+ manifest = " s.files = %w[\n#{files}\n ]\n"
118
+ spec = [head, manifest, tail].join(" # = MANIFEST =\n")
119
+ File.open(gemspec_file, 'w') { |io| io.write(spec) }
120
+ puts "Updated #{gemspec_file}"
121
+ end
122
+
123
+ desc "Validate #{gemspec_file}"
124
+ task :validate do
125
+ libfiles = Dir['lib/*'] - ["lib/#{name}.rb", "lib/#{name}"]
126
+ unless libfiles.empty?
127
+ puts "Directory `lib` should only contain a `#{name}.rb` file and `#{name}` dir."
128
+ #exit!
129
+ end
130
+ unless Dir['VERSION*'].empty?
131
+ puts "A `VERSION` file at root level violates Gem best practices."
132
+ exit!
133
+ end
134
+ end
@@ -0,0 +1,14 @@
1
+ require File.expand_path("../../tainted_hash", __FILE__)
2
+
3
+ TaintedHash.send :include, TaintedHash::RailsMethods
4
+
5
+ module TaintedHash::Controller
6
+ def wrap_params_with_tainted_hash
7
+ @_params = TaintedHash.new(@_params.to_hash)
8
+ end
9
+ end
10
+
11
+ if defined?(ActionController::Base)
12
+ ActionController::Base.send :include, TaintedHash::Controller
13
+ end
14
+
@@ -0,0 +1,238 @@
1
+ require 'set'
2
+
3
+ class TaintedHash < Hash
4
+ VERSION = "0.0.1"
5
+
6
+ class UnexposedError < StandardError
7
+ # Builds an exception when a TaintedHash has some unexposed keys. Useful
8
+ # for testing and production notification of weird parameters.
9
+ def initialize(action, extras)
10
+ @action = action
11
+ @extras = extras
12
+ super("Extra params for #{@action} in tainted hash: #{@extras.inspect}")
13
+ end
14
+ end
15
+
16
+ def self.on_no_expose(&block)
17
+ @on_no_expose = block
18
+ end
19
+
20
+ def self.trigger_no_expose(hash)
21
+ @on_no_expose.call hash if @on_no_expose && (!block_given? || yield)
22
+ end
23
+
24
+ # Public: Gets the original hash that is being wrapped.
25
+ #
26
+ # Returns a Hash.
27
+ attr_reader :original_hash
28
+
29
+ # A Tainted Hash only exposes expected keys. You can either expose them
30
+ # manually, or through common Hash methods like #values_at or #slice. Once
31
+ # created, the internal Hash is frozen from future updates.
32
+ #
33
+ # hash - Optional Hash used internally.
34
+ # new_class - Optional class used to create basic Hashes. Default: Hash.
35
+ #
36
+ def initialize(hash = nil, new_class = nil)
37
+ (@original_hash = hash || {}).keys.each do |key|
38
+ key_s = key.to_s
39
+ next if key_s == key
40
+ @original_hash[key_s] = @original_hash.delete(key)
41
+ end
42
+
43
+ @new_class = new_class || Hash
44
+ @exposed_nothing = true
45
+ end
46
+
47
+ # Public: Exposes one or more keys for the hash.
48
+ #
49
+ # *keys - One or more String keys.
50
+ #
51
+ # Returns this TaintedHash.
52
+ def expose(*keys)
53
+ @exposed_nothing = false
54
+ keys.each do |key|
55
+ key_s = key.to_s
56
+ self[key_s] = @original_hash[key_s] if @original_hash.key?(key_s)
57
+ end
58
+ self
59
+ end
60
+
61
+ # Public: Exposes every key of the hash.
62
+ #
63
+ # Returns this TaintedHash.
64
+ def expose_all
65
+ @exposed_nothing = false
66
+ @original_hash.each do |key, value|
67
+ self[key] = value
68
+ end
69
+ self
70
+ end
71
+
72
+ # Public: Gets the unexposed keys from the original Hash.
73
+ #
74
+ # Returns an Array of String keys.
75
+ def extra_keys
76
+ @original_hash.keys - self.keys
77
+ end
78
+
79
+ # Public: Fetches the value in the hash at key, or a sensible default.
80
+ #
81
+ # key - A String key to retrieve.
82
+ # default - A sensible default.
83
+ #
84
+ # Returns the value of the key, or the default.
85
+ def fetch(key, default = nil)
86
+ key_s = key.to_s
87
+ @original_hash.fetch key_s, default
88
+ end
89
+
90
+ # Public: Gets the value for the key, and exposes the key for the Hash.
91
+ #
92
+ # key - A String key to retrieve.
93
+ #
94
+ # Returns the value of at the key in Hash.
95
+ def [](key)
96
+ key_s = key.to_s
97
+ return if !@original_hash.key?(key_s)
98
+
99
+ case value = @original_hash[key_s]
100
+ when TaintedHash then value
101
+ when Hash
102
+ value = @original_hash[key_s] = self.class.new(value, @new_class)
103
+ else value
104
+ end
105
+ end
106
+
107
+ # Public: Attempts to set the key of a frozen hash.
108
+ #
109
+ # key - String key to set.
110
+ # value - Value of the key.
111
+ #
112
+ # Returns nothing
113
+ def []=(key, value)
114
+ key_s = key.to_s
115
+ super(key_s, @original_hash[key_s] = case value
116
+ when TaintedHash then value
117
+ when Hash then self.class.new(value, @new_class)
118
+ else value
119
+ end)
120
+ end
121
+
122
+ # Public: Deletes the value from both the internal and current Hash.
123
+ #
124
+ # key - A String key to delete.
125
+ #
126
+ # Returns the value from the key.
127
+ def delete(key)
128
+ key_s = key.to_s
129
+ super(key_s)
130
+ @original_hash.delete key_s
131
+ end
132
+
133
+ # Public: Checks whether the given key has been exposed or not.
134
+ #
135
+ # key - A String key.
136
+ #
137
+ # Returns true if exposed, or false.
138
+ def include?(key)
139
+ super(key.to_s)
140
+ end
141
+
142
+ alias key? include?
143
+
144
+ # Public: Returns the values for the given keys, and exposes the keys.
145
+ #
146
+ # *keys - One or more String keys.
147
+ #
148
+ # Returns an Array of the values (or nil if there is no value) for the keys.
149
+ def values_at(*keys)
150
+ @original_hash.values_at *keys.map { |k| k.to_s }
151
+ end
152
+
153
+ # Public: Merges the given hash with the internal and a dup of the current
154
+ # Hash.
155
+ #
156
+ # hash - A Hash with String keys.
157
+ #
158
+ # Returns a dup of this TaintedHash.
159
+ def merge(hash)
160
+ dup.update(hash)
161
+ end
162
+
163
+ # Public: Updates the internal and current Hash with the given Hash.
164
+ #
165
+ # hash - A Hash with String keys.
166
+ #
167
+ # Returns this TaintedHash.
168
+ def update(hash)
169
+ hash.each do |key, value|
170
+ @original_hash[key.to_s] = value
171
+ end
172
+ self
173
+ end
174
+
175
+ alias merge! update
176
+
177
+ # Public: Enumerates through the exposed keys and valuesfor the hash.
178
+ #
179
+ # Yields the String key, and the value.
180
+ #
181
+ # Returns nothing.
182
+ def each
183
+ self.class.trigger_no_expose(self) { @exposed_nothing && size.zero? }
184
+ block = block_given? ? Proc.new : nil
185
+ super(&block)
186
+ end
187
+
188
+ # Public: Builds a normal Hash of the exposed values from this hash.
189
+ #
190
+ # Returns a Hash.
191
+ def to_hash
192
+ hash = @new_class.new
193
+ each do |key, value|
194
+ hash[key] = case value
195
+ when TaintedHash then value.to_hash
196
+ else value
197
+ end
198
+ end
199
+ hash
200
+ end
201
+
202
+ def inspect
203
+ %(#<#{self.class}:#{object_id} @hash=#{@original_hash.inspect} @exposed=#{keys.inspect}>)
204
+ end
205
+
206
+ module RailsMethods
207
+ def self.included(base)
208
+ base.send :alias_method, :stringify_keys!, :stringify_keys
209
+ end
210
+
211
+ # Public: Returns a portion of the Hash.
212
+ #
213
+ # *keys - One or more String keys.
214
+ #
215
+ # Returns a Hash of the requested keys and values.
216
+ def slice(*keys)
217
+ str_keys = @original_hash.keys & keys.map { |k| k.to_s }
218
+ hash = self.class.new
219
+ str_keys.each do |key|
220
+ hash[key] = @original_hash[key]
221
+ end
222
+ hash
223
+ end
224
+
225
+ def slice!(*keys)
226
+ raise NotImplementedError
227
+ end
228
+
229
+ def stringify_keys
230
+ self
231
+ end
232
+
233
+ def to_query
234
+ @original_hash.to_query
235
+ end
236
+ end
237
+ end
238
+
@@ -0,0 +1,55 @@
1
+ ## This is the rakegem gemspec template. Make sure you read and understand
2
+ ## all of the comments. Some sections require modification, and others can
3
+ ## be deleted if you don't need them. Once you understand the contents of
4
+ ## this file, feel free to delete any comments that begin with two hash marks.
5
+ ## You can find comprehensive Gem::Specification documentation, at
6
+ ## http://docs.rubygems.org/read/chapter/20
7
+ Gem::Specification.new do |s|
8
+ s.specification_version = 2 if s.respond_to? :specification_version=
9
+ s.required_rubygems_version = Gem::Requirement.new(">= 1.3.5") if s.respond_to? :required_rubygems_version=
10
+
11
+ ## Leave these as is they will be modified for you by the rake gemspec task.
12
+ ## If your rubyforge_project name is different, then edit it and comment out
13
+ ## the sub! line in the Rakefile
14
+ s.name = 'tainted_hash'
15
+ s.version = '0.0.1'
16
+ s.date = '2012-03-18'
17
+ s.rubyforge_project = 'tainted_hash'
18
+
19
+ ## Make sure your summary is short. The description may be as long
20
+ ## as you like.
21
+ s.summary = "Hash wrapper."
22
+ s.description = "Hash wrapper."
23
+
24
+ ## List the primary authors. If there are a bunch of authors, it's probably
25
+ ## better to set the email to an email list or something. If you don't have
26
+ ## a custom homepage, consider using your GitHub URL or the like.
27
+ s.authors = ["Rick Olson"]
28
+ s.email = 'technoweenie@gmail.com'
29
+ s.homepage = 'https://github.com/technoweenie/tainted_hash'
30
+
31
+ ## This gets added to the $LOAD_PATH so that 'lib/NAME.rb' can be required as
32
+ ## require 'NAME.rb' or'/lib/NAME/file.rb' can be as require 'NAME/file.rb'
33
+ s.require_paths = %w[lib]
34
+
35
+ s.add_development_dependency 'test-unit'
36
+
37
+ ## Leave this section as-is. It will be automatically generated from the
38
+ ## contents of your Git repository via the gemspec task. DO NOT REMOVE
39
+ ## THE MANIFEST COMMENTS, they are used as delimiters by the task.
40
+ # = MANIFEST =
41
+ s.files = %w[
42
+ LICENSE.md
43
+ README.md
44
+ Rakefile
45
+ lib/tainted_hash.rb
46
+ lib/tainted_hash/rails.rb
47
+ tainted_hash.gemspec
48
+ test/tainted_hash_test.rb
49
+ ]
50
+ # = MANIFEST =
51
+
52
+ ## Test files will be grabbed from the file list. Make sure the path glob
53
+ ## matches what you actually use.
54
+ s.test_files = s.files.select { |path| path =~ %r{^test/*/.+\.rb} }
55
+ end
@@ -0,0 +1,185 @@
1
+ require File.expand_path("../../lib/tainted_hash", __FILE__)
2
+ require 'test/unit'
3
+
4
+ TaintedHash.send :include, TaintedHash::RailsMethods
5
+ TaintedHash.on_no_expose do |hash|
6
+ raise
7
+ end
8
+
9
+ class TaintedHashTest < Test::Unit::TestCase
10
+ def setup
11
+ @hash = {'a' => 1, 'b' => 2, 'c' => {'name' => 'bob'}}
12
+ @tainted = TaintedHash.new @hash
13
+ end
14
+
15
+ def test_exposes_no_keys_by_default
16
+ assert !@tainted.include?('a')
17
+ assert !@tainted.include?('b')
18
+ assert_equal [], @tainted.keys
19
+
20
+ assert @hash.include?('a')
21
+ assert @hash.include?('b')
22
+ assert @hash.include?('c')
23
+ end
24
+
25
+ def test_dup
26
+ @tainted[:c].expose :name
27
+ @tainted.expose :a
28
+ dup = @tainted.dup.expose :b
29
+ assert_equal %w(a), @tainted.keys.sort
30
+ assert_equal %w(a b), dup.keys.sort
31
+ end
32
+
33
+ def test_expose_keys
34
+ assert !@tainted.include?(:a)
35
+ assert_equal [], @tainted.keys
36
+ @tainted.expose :a
37
+ assert @tainted.include?(:a)
38
+ assert_equal %w(a), @tainted.keys
39
+ end
40
+
41
+ def test_fetching_a_value
42
+ assert !@tainted.include?(:a)
43
+ assert !@tainted.include?(:d)
44
+ assert_equal 1, @tainted.fetch(:a, :default)
45
+ assert_equal :default, @tainted.fetch(:d, :default)
46
+ assert !@tainted.include?(:a)
47
+ assert !@tainted.include?(:d)
48
+ end
49
+
50
+ def test_getting_a_value
51
+ assert !@tainted.include?(:a)
52
+ assert_equal 1, @tainted[:a]
53
+ assert !@tainted.include?(:a)
54
+ end
55
+
56
+ def test_setting_a_value
57
+ assert !@tainted.include?(:a)
58
+ @tainted[:a] = 2
59
+ assert @tainted.include?(:a)
60
+ assert_equal 2, @tainted[:a]
61
+ end
62
+
63
+ def test_deleting_a_value
64
+ assert_equal 1, @tainted[:a]
65
+ assert !@tainted.include?(:a)
66
+ assert_equal 1, @tainted.delete(:a)
67
+ assert !@tainted.include?(:a)
68
+ end
69
+
70
+ def test_slicing_a_hash
71
+ assert !@tainted.include?(:a)
72
+ assert !@tainted.include?(:b)
73
+ assert !@tainted.include?(:c)
74
+
75
+ output = @tainted.slice(:a, :b)
76
+ assert_equal({'a' => 1, 'b' => 2}, output.to_hash)
77
+
78
+ assert !@tainted.include?(:a)
79
+ assert !@tainted.include?(:b)
80
+ assert !@tainted.include?(:c)
81
+
82
+ assert output.include?(:a)
83
+ assert output.include?(:b)
84
+ assert !output.include?(:c)
85
+ end
86
+
87
+ def test_yields_real_values
88
+ @tainted[:c].expose_all
89
+ @tainted.expose(:a).each do |key, value|
90
+ case key
91
+ when 'a' then assert_equal(1, value)
92
+ when 'c'
93
+ assert_equal({"name" => "bob"}, value)
94
+ assert_kind_of TaintedHash, value
95
+ end
96
+ end
97
+ end
98
+
99
+ def test_nested_hash_has_tainted_hashes
100
+ assert_kind_of TaintedHash, @tainted[:c]
101
+ assert_equal 'bob', @tainted[:c][:name]
102
+ end
103
+
104
+ def test_slicing_nested_hashes
105
+ slice = @tainted.slice :b, :c
106
+ assert_equal 2, slice[:b]
107
+ assert_equal 'bob', slice[:c][:name]
108
+ assert_equal %w(b c), slice.keys.sort
109
+ assert_equal [], slice[:c].keys
110
+ end
111
+
112
+ def test_slicing_and_building_hashes
113
+ hash = {'desc' => 'abc', 'files' => {'abc.txt' => 'abc'}}
114
+ tainted = TaintedHash.new hash
115
+
116
+ tainted.expose :desc, :files
117
+ assert tainted.include?(:desc)
118
+ assert tainted.include?(:files)
119
+
120
+ slice = tainted.slice :desc
121
+ assert slice.include?(:desc)
122
+ assert !slice.include?(:files)
123
+ assert_equal %w(desc), slice.keys
124
+ slice[:contents] = tainted[:files].expose_all
125
+ assert slice[:contents].include?('abc.txt')
126
+ assert_equal 'abc', slice[:contents]['abc.txt']
127
+ end
128
+
129
+ def test_update_hash
130
+ assert !@tainted.include?(:a)
131
+ assert !@tainted.include?(:d)
132
+ @tainted.update :a => 2, :d => 1
133
+ assert !@tainted.include?(:a)
134
+ assert !@tainted.include?(:d)
135
+ assert_equal 2, @tainted[:a]
136
+ assert_equal 1, @tainted[:d]
137
+ end
138
+
139
+ def test_does_not_expose_missing_keys
140
+ assert !@tainted.include?(:a)
141
+ assert !@tainted.include?(:d)
142
+ @tainted.expose :a, :d
143
+ assert @tainted.include?(:a)
144
+ assert !@tainted.include?(:d)
145
+ end
146
+
147
+ def test_values_at_doesnt_expose_keys
148
+ assert !@tainted.include?(:a)
149
+ assert !@tainted.include?(:b)
150
+ assert !@tainted.include?(:d)
151
+ assert_equal [1,2, nil], @tainted.values_at(:a, :b, :d)
152
+ assert !@tainted.include?(:a)
153
+ assert !@tainted.include?(:b)
154
+ assert !@tainted.include?(:d)
155
+ end
156
+
157
+ def test_requires_something_to_be_exposed
158
+ assert_raises RuntimeError do
159
+ @tainted.to_hash
160
+ end
161
+
162
+ @tainted.expose :missing
163
+ assert_equal({}, @tainted.to_hash)
164
+ @tainted.expose :a
165
+ assert_equal({'a' => 1}, @tainted.to_hash)
166
+ end
167
+
168
+ def test_works_with_integer_keys
169
+ hash = {'a' => 1, 1 => :a}
170
+ tainted = TaintedHash.new hash
171
+ assert_equal 1, tainted[:a]
172
+ assert_equal :a, tainted[1]
173
+ end
174
+
175
+ def test_gets_extra_keys
176
+ assert_equal %w(a b c), @tainted.extra_keys.sort
177
+ assert_equal %w(b c), @tainted.expose(:a).extra_keys.sort
178
+ end
179
+
180
+ def test_slice_doesnt_include_missing_keys
181
+ slice = @tainted.slice :a, :d
182
+ assert_equal({'a' => 1}, slice.to_hash)
183
+ end
184
+ end
185
+
metadata ADDED
@@ -0,0 +1,86 @@
1
+ --- !ruby/object:Gem::Specification
2
+ name: tainted_hash
3
+ version: !ruby/object:Gem::Version
4
+ hash: 29
5
+ prerelease:
6
+ segments:
7
+ - 0
8
+ - 0
9
+ - 1
10
+ version: 0.0.1
11
+ platform: ruby
12
+ authors:
13
+ - Rick Olson
14
+ autorequire:
15
+ bindir: bin
16
+ cert_chain: []
17
+
18
+ date: 2012-03-18 00:00:00 Z
19
+ dependencies:
20
+ - !ruby/object:Gem::Dependency
21
+ name: test-unit
22
+ prerelease: false
23
+ requirement: &id001 !ruby/object:Gem::Requirement
24
+ none: false
25
+ requirements:
26
+ - - ">="
27
+ - !ruby/object:Gem::Version
28
+ hash: 3
29
+ segments:
30
+ - 0
31
+ version: "0"
32
+ type: :development
33
+ version_requirements: *id001
34
+ description: Hash wrapper.
35
+ email: technoweenie@gmail.com
36
+ executables: []
37
+
38
+ extensions: []
39
+
40
+ extra_rdoc_files: []
41
+
42
+ files:
43
+ - LICENSE.md
44
+ - README.md
45
+ - Rakefile
46
+ - lib/tainted_hash.rb
47
+ - lib/tainted_hash/rails.rb
48
+ - tainted_hash.gemspec
49
+ - test/tainted_hash_test.rb
50
+ homepage: https://github.com/technoweenie/tainted_hash
51
+ licenses: []
52
+
53
+ post_install_message:
54
+ rdoc_options: []
55
+
56
+ require_paths:
57
+ - lib
58
+ required_ruby_version: !ruby/object:Gem::Requirement
59
+ none: false
60
+ requirements:
61
+ - - ">="
62
+ - !ruby/object:Gem::Version
63
+ hash: 3
64
+ segments:
65
+ - 0
66
+ version: "0"
67
+ required_rubygems_version: !ruby/object:Gem::Requirement
68
+ none: false
69
+ requirements:
70
+ - - ">="
71
+ - !ruby/object:Gem::Version
72
+ hash: 17
73
+ segments:
74
+ - 1
75
+ - 3
76
+ - 5
77
+ version: 1.3.5
78
+ requirements: []
79
+
80
+ rubyforge_project: tainted_hash
81
+ rubygems_version: 1.8.10
82
+ signing_key:
83
+ specification_version: 2
84
+ summary: Hash wrapper.
85
+ test_files:
86
+ - test/tainted_hash_test.rb