tainted_hash 0.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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