Skip to content

Commit a22acc1

Browse files
committed
Merge pull request #687 from estolfo/RUBY-1021-sensitive-opts
RUBY-1021 Use Redacted options class to redact option fields when printing
2 parents 8d8b03a + 01c9718 commit a22acc1

24 files changed

+689
-62
lines changed

lib/mongo.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
require 'forwardable'
1616
require 'bson'
1717
require 'openssl'
18+
require 'mongo/options'
1819
require 'mongo/loggable'
1920
require 'mongo/monitoring'
2021
require 'mongo/logger'
@@ -32,7 +33,6 @@
3233
require 'mongo/dbref'
3334
require 'mongo/grid'
3435
require 'mongo/index'
35-
require 'mongo/options'
3636
require 'mongo/protocol'
3737
require 'mongo/server'
3838
require 'mongo/server_selector'

lib/mongo/client.rb

Lines changed: 9 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -153,7 +153,7 @@ def hash
153153
# logs at the default 250 characters.
154154
#
155155
# @since 2.0.0
156-
def initialize(addresses_or_uri, options = {})
156+
def initialize(addresses_or_uri, options = Options::Redacted.new)
157157
@monitoring = Monitoring.new(options)
158158
if addresses_or_uri.is_a?(::String)
159159
create_from_uri(addresses_or_uri, options)
@@ -185,7 +185,7 @@ def inspect
185185
#
186186
# @since 2.0.0
187187
def read_preference
188-
@read_preference ||= ServerSelector.get((options[:read] || {}).merge(options))
188+
@read_preference ||= ServerSelector.get(Options::Redacted.new(options[:read] || {}).merge(options))
189189
end
190190

191191
# Use the database with the provided name. This will switch the current
@@ -215,9 +215,9 @@ def use(name)
215215
# @return [ Mongo::Client ] A new client instance.
216216
#
217217
# @since 2.0.0
218-
def with(new_options = {})
218+
def with(new_options = Options::Redacted.new)
219219
clone.tap do |client|
220-
opts = new_options || {}
220+
opts = Options::Redacted.new(new_options) || Options::Redacted.new
221221
client.options.update(opts)
222222
Database.create(client)
223223
# We can't use the same cluster if some options that would affect it
@@ -291,15 +291,15 @@ def list_databases
291291

292292
private
293293

294-
def create_from_addresses(addresses, opts = {})
295-
@options = Database::DEFAULT_OPTIONS.merge(opts).freeze
294+
def create_from_addresses(addresses, opts = Options::Redacted.new)
295+
@options = Options::Redacted.new(Database::DEFAULT_OPTIONS.merge(opts)).freeze
296296
@cluster = Cluster.new(addresses, @monitoring, options)
297297
@database = Database.new(self, options[:database], options)
298298
end
299299

300-
def create_from_uri(connection_string, opts = {})
300+
def create_from_uri(connection_string, opts = Options::Redacted.new)
301301
uri = URI.new(connection_string, opts)
302-
@options = Database::DEFAULT_OPTIONS.merge(uri.client_options.merge(opts)).freeze
302+
@options = Options::Redacted.new(Database::DEFAULT_OPTIONS.merge(uri.client_options.merge(opts))).freeze
303303
@cluster = Cluster.new(uri.servers, @monitoring, options)
304304
@database = Database.new(self, options[:database], options)
305305
end
@@ -314,7 +314,7 @@ def initialize_copy(original)
314314

315315
def cluster_modifying?(new_options)
316316
cluster_options = new_options.reject do |name|
317-
CRUD_OPTIONS.include?(name)
317+
CRUD_OPTIONS.include?(name.to_sym)
318318
end
319319
cluster_options.any? do |name, value|
320320
options[name] != value

lib/mongo/cluster.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,7 @@ def add(host)
8888
# @param [ Hash ] options The options.
8989
#
9090
# @since 2.0.0
91-
def initialize(seeds, monitoring, options = {})
91+
def initialize(seeds, monitoring, options = Options::Redacted.new)
9292
@addresses = []
9393
@servers = []
9494
@monitoring = monitoring
@@ -125,7 +125,7 @@ def inspect
125125
#
126126
# @since 2.0.0
127127
def next_primary
128-
ServerSelector.get({ mode: :primary }.merge(options)).select_server(self)
128+
ServerSelector.get(ServerSelector::PRIMARY.merge(options)).select_server(self)
129129
end
130130

131131
# Elect a primary server from the description that has just changed to a

lib/mongo/collection/view/readable.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -298,7 +298,8 @@ def projection(document = nil)
298298
# @since 2.0.0
299299
def read(value = nil)
300300
return default_read if value.nil?
301-
configure(:read, value.is_a?(Hash) ? ServerSelector.get(value) : value)
301+
selector = value.is_a?(Hash) ? ServerSelector.get(client.options.merge(value)) : value
302+
configure(:read, selector)
302303
end
303304

304305
# Set whether to return only the indexed field or fields.

lib/mongo/database.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ class Database
3636
# The default database options.
3737
#
3838
# @since 2.0.0
39-
DEFAULT_OPTIONS = { :database => ADMIN }.freeze
39+
DEFAULT_OPTIONS = Options::Redacted.new(:database => ADMIN).freeze
4040

4141
# Database name field constant.
4242
#
@@ -148,7 +148,7 @@ def collections
148148
#
149149
# @return [ Hash ] The result of the command execution.
150150
def command(operation, opts = {})
151-
preference = opts[:read] ? ServerSelector.get(opts[:read].merge(options)) : read_preference
151+
preference = opts[:read] ? ServerSelector.get(client.options.merge(opts[:read])) : read_preference
152152
server = preference.select_server(cluster)
153153
Operation::Command.new({
154154
:selector => operation,

lib/mongo/grid/fs_bucket.rb

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ module Grid
1919
#
2020
# @since 2.0.0
2121
class FSBucket
22+
extend Forwardable
2223

2324
# The default root prefix.
2425
#
@@ -55,6 +56,12 @@ class FSBucket
5556
# @since 2.1.0
5657
attr_reader :options
5758

59+
# Get client from the database.
60+
#
61+
# @since 2.1.0
62+
def_delegators :database,
63+
:client
64+
5865
# Find files collection documents matching a given selector.
5966
#
6067
# @example Find files collection documents by a filename.
@@ -395,7 +402,7 @@ def upload_from_stream(filename, io, opts = {})
395402
# @since 2.1.0
396403
def read_preference
397404
@read_preference ||= @options[:read] ?
398-
ServerSelector.get((@options[:read] || {}).merge(database.options)) :
405+
ServerSelector.get(Options::Redacted.new((@options[:read] || {}).merge(client.options))) :
399406
database.read_preference
400407
end
401408

lib/mongo/grid/stream/read.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -134,7 +134,7 @@ def closed?
134134
# @since 2.1.0
135135
def read_preference
136136
@read_preference ||= @options[:read] ?
137-
ServerSelector.get((@options[:read] || {}).merge(fs.options)) :
137+
ServerSelector.get(Options::Redacted.new((@options[:read] || {}).merge(fs.options))) :
138138
fs.read_preference
139139
end
140140

lib/mongo/options.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,4 @@
1313
# limitations under the License.
1414

1515
require 'mongo/options/mapper'
16+
require 'mongo/options/redacted'

lib/mongo/options/redacted.rb

Lines changed: 156 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,156 @@
1+
# Copyright (C) 2015 MongoDB, Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
module Mongo
16+
module Options
17+
18+
# Class for wrapping options that could be sensitive.
19+
# When printed, the sensitive values will be redacted.
20+
#
21+
# @since 2.1.0
22+
class Redacted < BSON::Document
23+
24+
# The options whose values will be redacted.
25+
#
26+
# @since 2.1.0
27+
SENSITIVE_OPTIONS = [ :password,
28+
:pwd ].freeze
29+
30+
# The replacement string used in place of the value for sensitive keys.
31+
#
32+
# @since 2.1.0
33+
STRING_REPLACEMENT = '<REDACTED>'.freeze
34+
35+
# Get a string representation of the options.
36+
#
37+
# @return [ String ] The string representation of the options.
38+
#
39+
# @since 2.1.0
40+
def inspect
41+
redacted_string(:inspect)
42+
end
43+
44+
# Get a string representation of the options.
45+
#
46+
# @return [ String ] The string representation of the options.
47+
#
48+
# @since 2.1.0
49+
def to_s
50+
redacted_string(:to_s)
51+
end
52+
53+
# Whether these options contain a given key.
54+
#
55+
# @example Determine if the options contain a given key.
56+
# options.has_key?(:name)
57+
#
58+
# @param [ String, Symbol ] key The key to check for existence.
59+
#
60+
# @return [ true, false ] If the options contain the given key.
61+
#
62+
# @since 2.1.0
63+
def has_key?(key)
64+
super(convert_key(key))
65+
end
66+
alias_method :key?, :has_key?
67+
68+
# Returns a new options object consisting of pairs for which the block returns false.
69+
#
70+
# @example Get a new options object with pairs for which the block returns false.
71+
# new_options = options.reject { |k, v| k == 'database' }
72+
#
73+
# @yieldparam [ String, Object ] The key as a string and its value.
74+
#
75+
# @return [ Options::Redacted ] A new options object.
76+
#
77+
# @since 2.1.0
78+
def reject(&block)
79+
new_options = dup
80+
new_options.reject!(&block) || new_options
81+
end
82+
83+
# Only keeps pairs for which the block returns false.
84+
#
85+
# @example Remove pairs from this object for which the block returns true.
86+
# options.reject! { |k, v| k == 'database' }
87+
#
88+
# @yieldparam [ String, Object ] The key as a string and its value.
89+
#
90+
# @return [ Options::Redacted, nil ] This object or nil if no changes were made.
91+
#
92+
# @since 2.1.0
93+
def reject!
94+
if block_given?
95+
n_keys = keys.size
96+
keys.each do |key|
97+
delete(key) if yield(key, self[key])
98+
end
99+
n_keys == keys.size ? nil : self
100+
else
101+
to_enum
102+
end
103+
end
104+
105+
# Returns a new options object consisting of pairs for which the block returns true.
106+
#
107+
# @example Get a new options object with pairs for which the block returns true.
108+
# ssl_options = options.select { |k, v| k =~ /ssl/ }
109+
#
110+
# @yieldparam [ String, Object ] The key as a string and its value.
111+
#
112+
# @return [ Options::Redacted ] A new options object.
113+
#
114+
# @since 2.1.0
115+
def select(&block)
116+
new_options = dup
117+
new_options.select!(&block) || new_options
118+
end
119+
120+
# Only keeps pairs for which the block returns true.
121+
#
122+
# @example Remove pairs from this object for which the block does not return true.
123+
# options.select! { |k, v| k =~ /ssl/ }
124+
#
125+
# @yieldparam [ String, Object ] The key as a string and its value.
126+
#
127+
# @return [ Options::Redacted, nil ] This object or nil if no changes were made.
128+
#
129+
# @since 2.1.0
130+
def select!
131+
if block_given?
132+
n_keys = keys.size
133+
keys.each do |key|
134+
delete(key) unless yield(key, self[key])
135+
end
136+
n_keys == keys.size ? nil : self
137+
else
138+
to_enum
139+
end
140+
end
141+
142+
private
143+
144+
def redacted_string(method)
145+
'{' + reduce([]) do |list, (k, v)|
146+
list << "#{k.send(method)}=>#{redact(k, v, method)}"
147+
end.join(', ') + '}'
148+
end
149+
150+
def redact(k, v, method)
151+
return STRING_REPLACEMENT if SENSITIVE_OPTIONS.include?(k.to_sym)
152+
v.send(method)
153+
end
154+
end
155+
end
156+
end

lib/mongo/server/connection.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -183,7 +183,7 @@ def deliver(messages)
183183
def setup_authentication!
184184
if options[:user]
185185
default_mechanism = @server.features.scram_sha_1_enabled? ? :scram : :mongodb_cr
186-
user = Auth::User.new({ :auth_mech => default_mechanism }.merge(options))
186+
user = Auth::User.new(Options::Redacted.new(:auth_mech => default_mechanism).merge(options))
187187
@authenticator = Auth.get(user)
188188
end
189189
end

lib/mongo/server_selector.rb

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -38,6 +38,11 @@ module ServerSelector
3838
# @since 2.0.0
3939
SERVER_SELECTION_TIMEOUT = 30.freeze
4040

41+
# Primary read preference.
42+
#
43+
# @since 2.1.0
44+
PRIMARY = Options::Redacted.new(mode: :primary).freeze
45+
4146
# Hash lookup for the selector classes based off the symbols
4247
# provided in configuration.
4348
#

lib/mongo/server_selector/selectable.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -61,10 +61,10 @@ def ==(other)
6161
#
6262
# @since 2.0.0
6363
def initialize(options = {})
64+
@options = (options || {}).freeze
6465
tag_sets = options[:tag_sets] || []
6566
validate_tag_sets!(tag_sets)
66-
@tag_sets = tag_sets
67-
@options = options
67+
@tag_sets = tag_sets.freeze
6868
end
6969

7070
# Select a server from eligible candidates.

lib/mongo/uri.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -268,7 +268,7 @@ def split_creds_hosts(string)
268268

269269
def parse_db_opts!(string)
270270
auth_db, d, uri_opts = string.partition(URI_OPTS_DELIM)
271-
@uri_options = parse_uri_options!(uri_opts)
271+
@uri_options = Options::Redacted.new(parse_uri_options!(uri_opts))
272272
@database = parse_database!(auth_db)
273273
end
274274

0 commit comments

Comments
 (0)