Skip to content

Commit b1f22cf

Browse files
committed
Added option to authenticate to a Mongo instance.
1 parent ab8bf84 commit b1f22cf

File tree

11 files changed

+140
-41
lines changed

11 files changed

+140
-41
lines changed

Gemfile.lock

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ PATH
33
specs:
44
msolr (0.1)
55
hamster
6+
highline
67
mongo (>= 1.3.1)
78
rsolr
89

@@ -12,6 +13,7 @@ GEM
1213
bson (1.3.1)
1314
builder (3.0.0)
1415
hamster (0.4.2)
16+
highline (1.6.2)
1517
mocha (0.9.12)
1618
mongo (1.3.1)
1719
bson (= 1.3.1)

bin/msolrd

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,7 @@ connection_opts = {
126126

127127
if (mongo_loc =~ /^mongodb:\/\//) then
128128
mongo = Mongo::Connection.from_uri(mongo_loc)
129+
authenticate_to_db(mongo, options.auth)
129130
connected_to_mongos = is_mongos?(mongo)
130131

131132
pool_size = options.conn_pool_size
@@ -143,6 +144,7 @@ else
143144
end
144145

145146
mongo = Mongo::Connection.new(mongo_host, mongo_port)
147+
authenticate_to_db(mongo, options.auth)
146148
connected_to_mongos = is_mongos?(mongo)
147149

148150
if options.conn_pool_size.nil? then

lib/msolr/argument_parser.rb

Lines changed: 51 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
require "rubygems"
12
require "optparse"
23
require "ostruct"
4+
require "highline"
35
require_relative "../version"
46

57
module MongoSolr
@@ -113,26 +115,34 @@ def self.parse_options(args, &block)
113115
exit
114116
end
115117

116-
# opts.separator ""
117-
# opts.on("-a", "--auth FILE_PATH",
118-
# "The file that contains authentication",
119-
# "credentials to the databases. The file",
120-
# "should have separate entries for each",
121-
# "database in one line with database",
122-
# "name, user name and password separated",
123-
# "by a comma. Sample file contents:",
124-
# " ",
125-
# "admin,root_user,root_password",
126-
# "local_db,user,strong_password",
127-
# " ",
128-
# "Note that spaces are valid characters in",
129-
# "user name and passwords so space characters",
130-
# "will not be ignored in the file. There are",
131-
# "currently issues for username and password",
132-
# "usernames that contains the comma (,)",
133-
# "character.") do |path|
134-
# options.auth = load_auth_file(path)
135-
# end
118+
opts.separator ""
119+
opts.on("--iauth",
120+
"Interactive authentication. A more secured ",
121+
"alternative to --auth. Prompts the admin ",
122+
"username and password.") do
123+
options.auth = prompt_password("admin")
124+
end
125+
126+
opts.separator ""
127+
opts.on("-a", "--auth FILE_PATH",
128+
"The file that contains authentication",
129+
"credentials to the databases. The file",
130+
"should have separate entries for each",
131+
"database in one line with database",
132+
"name, user name and password separated",
133+
"by a comma. Sample file contents:",
134+
" ",
135+
"admin,root_user,root_password",
136+
"local_db,user,strong_password",
137+
" ",
138+
"Note that spaces are valid characters in",
139+
"user name and passwords so space characters",
140+
"will not be ignored in the file. There are",
141+
"currently issues for username and password",
142+
"usernames that contains the comma (,)",
143+
"character.") do |path|
144+
options.auth = load_auth_file(path)
145+
end
136146

137147
yield opts, options if block_given?
138148

@@ -150,12 +160,14 @@ def self.parse_options(args, &block)
150160
############################################################################
151161
private
152162

153-
# Build a hash structure from the contents of an authentication file that conforms with
154-
# the formatting for the opt[:db_pass] parameter for SolrSynchronizer#sync.
163+
# Builds a hash structure from the contents of an authentication file that contains
164+
# the username and passwords for each database.
155165
#
156166
# @param file_path [String] the path of the authentication file.
157167
#
158168
# @return [Hash] the Hash object.
169+
#
170+
# @see Util::authenticate_to_db
159171
def self.load_auth_file(file_path)
160172
auth_data = {}
161173

@@ -170,6 +182,23 @@ def self.load_auth_file(file_path)
170182

171183
return auth_data
172184
end
185+
186+
# Prompts the user the username and password of a database.
187+
#
188+
# @param db_name [String] The name of the database to authenticate.
189+
#
190+
# @return [Hash] the Hash object containing the username and password.
191+
#
192+
# @see Util::authenticate_to_db
193+
def self.prompt_password(db_name)
194+
print("Please enter a username for #{db_name} db: ")
195+
user = gets
196+
197+
hl = HighLine.new
198+
pwd = hl.ask("Password: ") { |q| q.echo = "*" }
199+
200+
return { db_name => { :user => user.strip, :pwd => pwd.strip }}
201+
end
173202
end
174203
end
175204

lib/msolr/solr_synchronizer.rb

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -134,11 +134,6 @@ def update_config(opt = {}, &block)
134134
# cursor was updated.
135135
#
136136
# @raise [OplogException]
137-
#
138-
# Example:
139-
# auth = { "users" => { :user => "root", :pwd => "root" },
140-
# "admin" => { :user => "admin", :pwd => "" } }
141-
# solr.sync({ :db_pass => auth, :interval => 1 })
142137
def sync(&block)
143138
# Lock usage:
144139
# 1. @stop.mutex->@is_synching.mutex

msolr.gemspec

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,5 +26,6 @@ Gem::Specification.new do |s|
2626
s.add_dependency("rsolr")
2727
s.add_dependency("mongo", [">= 1.3.1"])
2828
s.add_dependency("hamster")
29+
s.add_dependency("highline")
2930
end
3031

test/js_plugin_wrapper.rb

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -26,14 +26,22 @@ def eval(js_code)
2626
#
2727
# @param db [String] The database name of the collection.
2828
# @param coll [String] The collection name.
29-
def index_to_solr(db, coll = "")
29+
# @param auth [Hash] Hash table containing the authentication details
30+
def index_to_solr(db, coll = "", auth = {})
31+
auth_line = ""
32+
33+
auth.each do |db_name, auth|
34+
auth_line << "db.getSiblingDB(\"#{db_name}\").auth(\"#{auth[:user]}\", \"#{auth[:pwd]}\");"
35+
end
36+
3037
if coll.empty? then
3138
index_line = "db.getSiblingDB(\"#{db}\").solrIndex();"
3239
else
3340
index_line = "db.getSiblingDB(\"#{db}\").#{coll}.solrIndex();"
3441
end
3542

36-
code = <<JAVASCRIPT
43+
code = auth_line
44+
code << <<JAVASCRIPT
3745
MSolr.connect("#{SOLR_LOC}");
3846
#{index_line}
3947
JAVASCRIPT

test/slow_tests/auth_file

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
admin,root,ugat

test/slow_tests/replica_set_test.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -198,7 +198,7 @@ def default_doc
198198
@js.index_to_solr(SMOKE_TEST_DB, @test_coll.name)
199199
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
200200

201-
run_daemon("-d #{@conn_str} --err_interval 0") do
201+
run_daemon("-d #{@conn_str} --err_interval 0") do |pio|
202202
solr_doc = nil
203203

204204
# This block is just for synchronization purposes and is used to make
@@ -228,7 +228,7 @@ def default_doc
228228
@js.index_to_solr(SMOKE_TEST_DB, @test_coll.name)
229229
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
230230

231-
run_daemon("-d #{@conn_str} --err_interval 0") do
231+
run_daemon("-d #{@conn_str} --err_interval 0") do |pio|
232232
solr_doc = nil
233233

234234
# This block is just for synchronization purposes and is used to make

test/slow_tests/sharding_test.rb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -87,7 +87,7 @@ def self.presplit(shard, mid_val, n)
8787
mock = mock()
8888
mock.expects(:daemon_end).once
8989

90-
run_daemon("-p #{ShardManager::MONGOS_PORT}") do
90+
run_daemon("-p #{ShardManager::MONGOS_PORT}") do |pio|
9191
@coll.insert({ SHARD_KEY => 7 })
9292
@coll.insert({ SHARD_KEY => 77 })
9393

@@ -109,7 +109,7 @@ def self.presplit(shard, mid_val, n)
109109
mock = mock()
110110
mock.expects(:daemon_end).once
111111

112-
run_daemon("-p #{ShardManager::MONGOS_PORT}") do
112+
run_daemon("-p #{ShardManager::MONGOS_PORT}") do |pio|
113113
@coll.remove(doc)
114114

115115
solr_doc = nil
@@ -130,7 +130,7 @@ def self.presplit(shard, mid_val, n)
130130
mock = mock()
131131
mock.expects(:daemon_end).once
132132

133-
run_daemon("-p #{ShardManager::MONGOS_PORT}") do
133+
run_daemon("-p #{ShardManager::MONGOS_PORT}") do |pio|
134134
midval = 60
135135
doc = { SHARD_KEY => midval }
136136
doc_id = @coll.insert(doc)

test/slow_tests/smoke_test.rb

Lines changed: 62 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def self.shutdown
6363
@js.index_to_solr(TEST_DB, @test_coll.name)
6464
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
6565

66-
run_daemon(DAEMON_ARGS) do
66+
run_daemon(DAEMON_ARGS) do |pio|
6767
solr_doc = nil
6868

6969
result = retry_until_true(TIMEOUT) do
@@ -82,7 +82,7 @@ def self.shutdown
8282
@js.index_to_solr(TEST_DB, @test_coll.name)
8383
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
8484

85-
run_daemon(DAEMON_ARGS) do
85+
run_daemon(DAEMON_ARGS) do |pio|
8686
solr_doc = nil
8787

8888
# This block is just for synchronization purposes and is used to make
@@ -113,7 +113,7 @@ def self.shutdown
113113
@js.index_to_solr(TEST_DB, @test_coll.name)
114114
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
115115

116-
run_daemon(DAEMON_ARGS) do
116+
run_daemon(DAEMON_ARGS) do |pio|
117117
solr_doc = nil
118118

119119
# This block is just for synchronization purposes and is used to make
@@ -142,6 +142,65 @@ def self.shutdown
142142
@mock.daemon_end
143143
end
144144
end
145+
146+
context "authentication" do
147+
setup do
148+
@admin_user = "root"
149+
@admin_pwd = "ugat"
150+
151+
@admin_db = @mongo_conn["admin"]
152+
@admin_db.add_user(@admin_user, @admin_pwd)
153+
@admin_db.authenticate(@admin_user, @admin_pwd)
154+
@auth = { "admin" => { :user => @admin_user, :pwd => @admin_pwd }}
155+
end
156+
157+
teardown do
158+
@admin_db.remove_user(@admin_user)
159+
end
160+
161+
should "be able to authenticate interactively" do
162+
@js.index_to_solr(TEST_DB, @test_coll.name, @auth)
163+
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
164+
165+
run_daemon(DAEMON_ARGS + " --iauth") do |pio|
166+
pio.write "#{@admin_user}\n"
167+
pio.write "#{@admin_pwd}\n"
168+
169+
solr_doc = nil
170+
result = retry_until_true(TIMEOUT) do
171+
response = @solr.select({ :params => { :q => SOLR_TEST_Q }})
172+
solr_doc = response["response"]["docs"].first
173+
not solr_doc.nil?
174+
end
175+
176+
assert(result, "Failed to index to Solr within #{TIMEOUT} seconds")
177+
assert_equal("hello", solr_doc["x"])
178+
179+
@mock.daemon_end
180+
end
181+
end
182+
183+
should "be able to authenticate using file" do
184+
auth_file_path = File.expand_path("../auth_file", __FILE__)
185+
186+
@js.index_to_solr(TEST_DB, @test_coll.name, @auth)
187+
@test_coll.insert(default_doc.merge({ :x => "hello" }), { :safe => true })
188+
189+
run_daemon(DAEMON_ARGS + " -a #{auth_file_path}") do |pio|
190+
solr_doc = nil
191+
result = retry_until_true(TIMEOUT) do
192+
response = @solr.select({ :params => { :q => SOLR_TEST_Q }})
193+
solr_doc = response["response"]["docs"].first
194+
not solr_doc.nil?
195+
end
196+
197+
assert(result, "Failed to index to Solr within #{TIMEOUT} seconds")
198+
assert_equal("hello", solr_doc["x"])
199+
200+
@mock.daemon_end
201+
end
202+
end
203+
end
145204
end
146205

147206
context "plugin" do

test/test_helper.rb

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,8 @@ def initialize
3939
def start
4040
if @pipe_io.nil? then
4141
FileUtils.mkdir_p DATA_DIR
42-
cmd = "mongod --master --dbpath #{DATA_DIR} --port #{PORT} --logpath /dev/null --quiet"
42+
cmd = "mongod --master --dbpath #{DATA_DIR} --port #{PORT} " +
43+
"--logpath /dev/null --quiet --auth"
4344
@pipe_io = IO.popen(cmd)
4445
end
4546
end
@@ -90,12 +91,13 @@ def retry_until_true(timeout, &block)
9091
# Run the Mongo-Solr daemon and terminate it.
9192
#
9293
# @param args [String] The arguments to pass to the daemon.
93-
# @param block [Proc] The procedure to execute before terminating the daemon.
94+
# @param block [Proc(pio)] The procedure to execute before terminating the daemon.
95+
# The piped IO to the daemon process is also passed to the block.
9496
def run_daemon(args = "", &block)
95-
daemon_pio = IO.popen("ruby #{PROJ_SRC_PATH}/../../bin/msolrd #{args} 2> /dev/null")
97+
daemon_pio = IO.popen("ruby #{PROJ_SRC_PATH}/../../bin/msolrd #{args} > /dev/null", "r+")
9698

9799
begin
98-
yield if block_given?
100+
yield daemon_pio if block_given?
99101
ensure
100102
Process.kill "TERM", daemon_pio.pid
101103
daemon_pio.close

0 commit comments

Comments
 (0)