Skip to content

Commit d350c7e

Browse files
p-mongop
authored andcommitted
RUBY-1889 Auth exceptions should include server information (#1588)
1 parent 6b06523 commit d350c7e

File tree

15 files changed

+161
-106
lines changed

15 files changed

+161
-106
lines changed

lib/mongo/auth.rb

Lines changed: 30 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -110,25 +110,45 @@ class Unauthorized < Mongo::Error::AuthError
110110
# @param [ String ] used_mechanism Auth mechanism actually used for
111111
# authentication. This is a full string like SCRAM-SHA-256.
112112
# @param [ String ] message The error message returned by the server.
113+
# @param [ Server ] server The server instance that authentication
114+
# was attempted against.
113115
#
114116
# @since 2.0.0
115-
def initialize(user, used_mechanism: nil, message: nil)
116-
specified_mechanism = if user.mechanism
117-
" (mechanism: #{user.mechanism})"
118-
else
119-
''
117+
def initialize(user, used_mechanism: nil, message: nil,
118+
server: nil
119+
)
120+
configured_bits = []
121+
used_bits = [
122+
"auth source: #{user.auth_source}",
123+
]
124+
125+
if user.mechanism
126+
configured_bits << "mechanism: #{user.mechanism}"
120127
end
121-
used_mechanism = if used_mechanism
122-
" (used mechanism: #{used_mechanism})"
123-
else
124-
''
128+
129+
if used_mechanism
130+
used_bits << "used mechanism: #{used_mechanism}"
131+
end
132+
133+
if server
134+
used_bits << "used server: #{server.address} (#{server.status})"
125135
end
136+
126137
used_user = if user.mechanism == :mongodb_x509
127138
'Client certificate'
128139
else
129140
"User #{user.name}"
130141
end
131-
msg = "#{used_user}#{specified_mechanism} is not authorized to access #{user.database} (auth source: #{user.auth_source})#{used_mechanism}"
142+
143+
if configured_bits.empty?
144+
configured_bits = ''
145+
else
146+
configured_bits = " (#{configured_bits.join(', ')})"
147+
end
148+
149+
used_bits = " (#{used_bits.join(', ')})"
150+
151+
msg = "#{used_user}#{configured_bits} is not authorized to access #{user.database}#{used_bits}"
132152
if message
133153
msg += ': ' + message
134154
end

lib/mongo/auth/cr.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ module Auth
2323
# @deprecated MONGODB-CR authentication mechanism is deprecated
2424
# as of MongoDB 3.6. Support for it in the Ruby driver will be
2525
# removed in driver version 3.0. Please use SCRAM instead.
26+
# @api private
2627
class CR
2728

2829
# The authentication mechinism string.

lib/mongo/auth/cr/conversation.rb

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -52,13 +52,14 @@ class Conversation
5252
#
5353
# @param [ Protocol::Message ] reply The reply of the previous
5454
# message.
55-
# @param [ Mongo::Server::Connection ] connection The connection being authenticated.
55+
# @param [ Mongo::Server::Connection ] connection The connection being
56+
# authenticated.
5657
#
5758
# @return [ Protocol::Query ] The next message to send.
5859
#
5960
# @since 2.0.0
60-
def continue(reply, connection = nil)
61-
validate!(reply)
61+
def continue(reply, connection)
62+
validate!(reply, connection.server)
6263
if connection && connection.features.op_msg_enabled?
6364
selector = LOGIN.merge(user: user.name, nonce: nonce, key: user.auth_key(nonce))
6465
selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source
@@ -78,29 +79,28 @@ def continue(reply, connection = nil)
7879
# Finalize the CR conversation. This is meant to be iterated until
7980
# the provided reply indicates the conversation is finished.
8081
#
81-
# @example Finalize the conversation.
82-
# conversation.finalize(reply)
83-
#
8482
# @param [ Protocol::Message ] reply The reply of the previous
8583
# message.
84+
# @param [ Server::Connection ] connection The connection being
85+
# authenticated.
8686
#
8787
# @return [ Protocol::Query ] The next message to send.
8888
#
8989
# @since 2.0.0
90-
def finalize(reply, connection = nil)
91-
validate!(reply)
90+
def finalize(reply, connection)
91+
validate!(reply, connection.server)
9292
end
9393

9494
# Start the CR conversation. This returns the first message that
9595
# needs to be sent to the server.
9696
#
97-
# @example Start the conversation.
98-
# conversation.start
97+
# @param [ Server::Connection ] connection The connection being
98+
# authenticated.
9999
#
100100
# @return [ Protocol::Query ] The first CR conversation message.
101101
#
102102
# @since 2.0.0
103-
def start(connection = nil)
103+
def start(connection)
104104
if connection && connection.features.op_msg_enabled?
105105
selector = Auth::GET_NONCE.merge(Protocol::Msg::DATABASE_IDENTIFIER => user.auth_source)
106106
cluster_time = connection.mongos? && connection.cluster_time
@@ -129,9 +129,9 @@ def initialize(user)
129129

130130
private
131131

132-
def validate!(reply)
132+
def validate!(reply, server)
133133
if reply.documents[0][Operation::Result::OK] != 1
134-
raise Unauthorized.new(user, used_mechanism: MECHANISM)
134+
raise Unauthorized.new(user, used_mechanism: MECHANISM, server: server)
135135
end
136136
@nonce = reply.documents[0][Auth::NONCE]
137137
@reply = reply

lib/mongo/auth/ldap.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Auth
2020
# Defines behavior for LDAP Proxy authentication.
2121
#
2222
# @since 2.0.0
23+
# @api private
2324
class LDAP
2425

2526
# The authentication mechinism string.
@@ -56,7 +57,7 @@ def login(connection)
5657
conversation = Conversation.new(user)
5758
reply = connection.dispatch([ conversation.start(connection) ])
5859
connection.update_cluster_time(Operation::Result.new(reply))
59-
conversation.finalize(reply)
60+
conversation.finalize(reply, connection)
6061
end
6162
end
6263
end

lib/mongo/auth/ldap/conversation.rb

Lines changed: 9 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -37,31 +37,28 @@ class Conversation
3737
# Finalize the PLAIN conversation. This is meant to be iterated until
3838
# the provided reply indicates the conversation is finished.
3939
#
40-
# @example Finalize the conversation.
41-
# conversation.finalize(reply)
42-
#
4340
# @param [ Protocol::Message ] reply The reply of the previous
4441
# message.
42+
# @param [ Server::Connection ] connection The connection being
43+
# authenticated.
4544
#
4645
# @return [ Protocol::Query ] The next message to send.
4746
#
4847
# @since 2.0.0
49-
def finalize(reply)
50-
validate!(reply)
48+
def finalize(reply, connection)
49+
validate!(reply, connection.server)
5150
end
5251

5352
# Start the PLAIN conversation. This returns the first message that
5453
# needs to be sent to the server.
5554
#
56-
# @example Start the conversation.
57-
# conversation.start
58-
#
59-
# @param [ Mongo::Server::Connection ] connection The connection being authenticated.
55+
# @param [ Server::Connection ] connection The connection being
56+
# authenticated.
6057
#
6158
# @return [ Protocol::Query ] The first PLAIN conversation message.
6259
#
6360
# @since 2.0.0
64-
def start(connection = nil)
61+
def start(connection)
6562
if connection && connection.features.op_msg_enabled?
6663
selector = LOGIN.merge(payload: payload, mechanism: LDAP::MECHANISM)
6764
selector[Protocol::Msg::DATABASE_IDENTIFIER] = Auth::EXTERNAL
@@ -96,9 +93,9 @@ def payload
9693
BSON::Binary.new("\x00#{user.name}\x00#{user.password}")
9794
end
9895

99-
def validate!(reply)
96+
def validate!(reply, server)
10097
if reply.documents[0][Operation::Result::OK] != 1
101-
raise Unauthorized.new(user, used_mechanism: MECHANISM)
98+
raise Unauthorized.new(user, used_mechanism: MECHANISM, server: server)
10299
end
103100
@reply = reply
104101
end

lib/mongo/auth/scram.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Auth
2020
# Defines behavior for SCRAM authentication.
2121
#
2222
# @since 2.0.0
23+
# @api private
2324
class SCRAM
2425

2526
# The authentication mechanism string for SCRAM-SHA-1.

lib/mongo/auth/scram/conversation.rb

Lines changed: 36 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ class SCRAM
2020
# the client and server.
2121
#
2222
# @since 2.0.0
23+
# @api private
2324
class Conversation
2425

2526
# The base client continue message.
@@ -103,13 +104,14 @@ class Conversation
103104
#
104105
# @param [ Protocol::Message ] reply The reply of the previous
105106
# message.
106-
# @param [ Mongo::Server::Connection ] connection The connection being authenticated.
107+
# @param [ Server::Connection ] connection The connection being
108+
# authenticated.
107109
#
108-
# @return [ Protocol::Query ] The next message to send.
110+
# @return [ Protocol::Message ] The next message to send.
109111
#
110112
# @since 2.0.0
111-
def continue(reply, connection = nil)
112-
validate_first_message!(reply)
113+
def continue(reply, connection)
114+
validate_first_message!(reply, connection.server)
113115

114116
# The salted password needs to be calculated now; otherwise, if the
115117
# client key is cached from a previous authentication, the salt in the
@@ -118,7 +120,10 @@ def continue(reply, connection = nil)
118120
salted_password
119121

120122
if connection && connection.features.op_msg_enabled?
121-
selector = CLIENT_CONTINUE_MESSAGE.merge(payload: client_final_message, conversationId: id)
123+
selector = CLIENT_CONTINUE_MESSAGE.merge(
124+
payload: client_final_message,
125+
conversationId: id,
126+
)
122127
selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source
123128
cluster_time = connection.mongos? && connection.cluster_time
124129
selector[Operation::CLUSTER_TIME] = cluster_time if cluster_time
@@ -127,29 +132,32 @@ def continue(reply, connection = nil)
127132
Protocol::Query.new(
128133
user.auth_source,
129134
Database::COMMAND,
130-
CLIENT_CONTINUE_MESSAGE.merge(payload: client_final_message, conversationId: id),
131-
limit: -1
135+
CLIENT_CONTINUE_MESSAGE.merge(
136+
payload: client_final_message,
137+
conversationId: id,
138+
),
139+
limit: -1,
132140
)
133141
end
134142
end
135143

136144
# Finalize the SCRAM conversation. This is meant to be iterated until
137145
# the provided reply indicates the conversation is finished.
138146
#
139-
# @example Finalize the conversation.
140-
# conversation.finalize(reply)
141-
#
142147
# @param [ Protocol::Message ] reply The reply of the previous
143148
# message.
144-
# @param [ Mongo::Server::Connection ] connection The connection being authenticated.
149+
# @param [ Server::Connection ] connection The connection being authenticated.
145150
#
146151
# @return [ Protocol::Query ] The next message to send.
147152
#
148153
# @since 2.0.0
149-
def finalize(reply, connection = nil)
150-
validate_final_message!(reply)
154+
def finalize(reply, connection)
155+
validate_final_message!(reply, connection.server)
151156
if connection && connection.features.op_msg_enabled?
152-
selector = CLIENT_CONTINUE_MESSAGE.merge(payload: client_empty_message, conversationId: id)
157+
selector = CLIENT_CONTINUE_MESSAGE.merge(
158+
payload: client_empty_message,
159+
conversationId: id,
160+
)
153161
selector[Protocol::Msg::DATABASE_IDENTIFIER] = user.auth_source
154162
cluster_time = connection.mongos? && connection.cluster_time
155163
selector[Operation::CLUSTER_TIME] = cluster_time if cluster_time
@@ -158,24 +166,24 @@ def finalize(reply, connection = nil)
158166
Protocol::Query.new(
159167
user.auth_source,
160168
Database::COMMAND,
161-
CLIENT_CONTINUE_MESSAGE.merge(payload: client_empty_message, conversationId: id),
162-
limit: -1
169+
CLIENT_CONTINUE_MESSAGE.merge(
170+
payload: client_empty_message,
171+
conversationId: id,
172+
),
173+
limit: -1,
163174
)
164175
end
165176
end
166177

167178
# Start the SCRAM conversation. This returns the first message that
168179
# needs to be sent to the server.
169180
#
170-
# @example Start the conversation.
171-
# conversation.start
172-
#
173-
# @param [ Mongo::Server::Connection ] connection The connection being authenticated.
181+
# @param [ Server::Connection ] connection The connection being authenticated.
174182
#
175183
# @return [ Protocol::Query ] The first SCRAM conversation message.
176184
#
177185
# @since 2.0.0
178-
def start(connection = nil)
186+
def start(connection)
179187
if connection && connection.features.op_msg_enabled?
180188
selector = CLIENT_FIRST_MESSAGE.merge(
181189
payload: client_first_message, mechanism: full_mechanism)
@@ -189,7 +197,7 @@ def start(connection = nil)
189197
Database::COMMAND,
190198
CLIENT_FIRST_MESSAGE.merge(
191199
payload: client_first_message, mechanism: full_mechanism),
192-
limit: -1
200+
limit: -1,
193201
)
194202
end
195203
end
@@ -505,23 +513,24 @@ def compare_digest(a, b)
505513
check == 0
506514
end
507515

508-
def validate_final_message!(reply)
509-
validate!(reply)
516+
def validate_final_message!(reply, server)
517+
validate!(reply, server)
510518
unless compare_digest(verifier, server_signature)
511519
raise Error::InvalidSignature.new(verifier, server_signature)
512520
end
513521
end
514522

515-
def validate_first_message!(reply)
516-
validate!(reply)
523+
def validate_first_message!(reply, server)
524+
validate!(reply, server)
517525
raise Error::InvalidNonce.new(nonce, rnonce) unless rnonce.start_with?(nonce)
518526
end
519527

520-
def validate!(reply)
528+
def validate!(reply, server)
521529
if reply.documents[0][Operation::Result::OK] != 1
522530
raise Unauthorized.new(user,
523531
used_mechanism: full_mechanism,
524532
message: reply.documents[0]['errmsg'],
533+
server: server,
525534
)
526535
end
527536
@reply = reply

lib/mongo/auth/x509.rb

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ module Auth
2020
# Defines behavior for X.509 authentication.
2121
#
2222
# @since 2.0.0
23+
# @api private
2324
class X509
2425

2526
# The authentication mechinism string.
@@ -67,7 +68,7 @@ def login(connection)
6768
conversation = Conversation.new(user)
6869
reply = connection.dispatch([ conversation.start(connection) ])
6970
connection.update_cluster_time(Operation::Result.new(reply))
70-
conversation.finalize(reply)
71+
conversation.finalize(reply, connection)
7172
end
7273
end
7374
end

0 commit comments

Comments
 (0)