File: auth_spec.rb

package info (click to toggle)
ruby-mongo 2.23.0-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 15,020 kB
  • sloc: ruby: 110,810; makefile: 5
file content (309 lines) | stat: -rw-r--r-- 9,731 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
# frozen_string_literal: true
# rubocop:todo all

require 'spec_helper'

describe 'Auth' do
  # User creation with a password fails on the server if, for example,
  # only MONGODB-AWS auth mechanism is allowed in server configuration.
  require_no_external_user

  describe 'Unauthorized exception message' do
    let(:server) do
      authorized_client.cluster.next_primary
    end

    let(:base_options) do
      SpecConfig.instance.monitoring_options.merge(connect: SpecConfig.instance.test_options[:connect])
    end

    let(:connection) do
      Mongo::Server::Connection.new(server, base_options.merge(options))
    end

    before(:all) do
      # If auth is configured, the test suite uses the configured user
      # and does not create its own users. However, the configured user may
      # not have the auth mechanisms we need. Therefore we create a user
      # for this test without specifying auth mechanisms, which gets us
      # server default (scram for 4.0, scram & scram256 for 4.2).

      users = ClientRegistry.instance.global_client('root_authorized').use(:admin).database.users
      unless users.info('existing_user').empty?
        users.remove('existing_user')
      end
      users.create('existing_user', password: 'password')
    end

    context 'user mechanism not provided' do

      context 'user does not exist' do
        let(:options) do
          {user: 'nonexistent_user' }
        end

        before do
          expect(connection.app_metadata.send(:document)[:saslSupportedMechs]).to eq('admin.nonexistent_user')
        end

        context 'scram-sha-1 only server' do
          min_server_fcv '3.0'
          max_server_version '3.6'

          it 'indicates scram-sha-1 was used' do
            expect do
              connection.connect!
            end.to raise_error(Mongo::Auth::Unauthorized, /User nonexistent_user \(mechanism: scram\) is not authorized to access admin.*used mechanism: SCRAM-SHA-1/)
          end
        end

        context 'scram-sha-256 server' do
          min_server_fcv '4.0'

          # An existing user on 4.0+ will negotiate scram-sha-256.
          # A non-existing user on 4.0+ will negotiate scram-sha-1.
          it 'indicates scram-sha-1 was used' do
            expect do
              connection.connect!
            end.to raise_error(Mongo::Auth::Unauthorized, /User nonexistent_user \(mechanism: scram\) is not authorized to access admin.*used mechanism: SCRAM-SHA-1/)
          end
        end
      end

      context 'user exists' do
        let(:options) do
          {user: 'existing_user', password: 'bogus'}
        end

        before do
          expect(connection.app_metadata.send(:document)[:saslSupportedMechs]).to eq("admin.existing_user")
        end

        context 'scram-sha-1 only server' do
          min_server_fcv '3.0'
          max_server_version '3.6'

          it 'indicates scram-sha-1 was used' do
            expect do
              connection.connect!
            end.to raise_error(Mongo::Auth::Unauthorized, /User existing_user \(mechanism: scram\) is not authorized to access admin.*used mechanism: SCRAM-SHA-1/)
          end
        end

        context 'scram-sha-256 server' do
          min_server_fcv '4.0'

          # An existing user on 4.0+ will negotiate scram-sha-256.
          # A non-existing user on 4.0+ will negotiate scram-sha-1.
          it 'indicates scram-sha-256 was used' do
            expect do
              connection.connect!
            end.to raise_error(Mongo::Auth::Unauthorized, /User existing_user \(mechanism: scram256\) is not authorized to access admin.*used mechanism: SCRAM-SHA-256/)
          end
        end
      end
    end

    context 'user mechanism is provided' do
      min_server_fcv '3.0'

      context 'scram-sha-1 requested' do
        let(:options) do
          {user: 'nonexistent_user', auth_mech: :scram}
        end

        it 'indicates scram-sha-1 was requested and used' do
          expect do
            connection.connect!
          end.to raise_error(Mongo::Auth::Unauthorized, /User nonexistent_user \(mechanism: scram\) is not authorized to access admin.*used mechanism: SCRAM-SHA-1/)
        end
      end

      context 'scram-sha-256 requested' do
        min_server_fcv '4.0'

        let(:options) do
          {user: 'nonexistent_user', auth_mech: :scram256}
        end

        it 'indicates scram-sha-256 was requested and used' do
          expect do
            connection.connect!
          end.to raise_error(Mongo::Auth::Unauthorized, /User nonexistent_user \(mechanism: scram256\) is not authorized to access admin.*used mechanism: SCRAM-SHA-256/)
        end
      end
    end

    context 'when authentication fails' do
      let(:options) do
        {user: 'nonexistent_user', password: 'foo'}
      end

      it 'reports which server authentication was attempted against' do
        expect do
          connection.connect!
        end.to raise_error(Mongo::Auth::Unauthorized, /used server: #{connection.address.to_s}/)
      end

      context 'with default auth source' do
        it 'reports auth source used' do
          expect do
            connection.connect!
          end.to raise_error(Mongo::Auth::Unauthorized, /auth source: admin/)
        end
      end

      context 'with custom auth source' do
        let(:options) do
          {user: 'nonexistent_user', password: 'foo', auth_source: 'authdb'}
        end

        it 'reports auth source used' do
          expect do
            connection.connect!
          end.to raise_error(Mongo::Auth::Unauthorized, /auth source: authdb/)
        end
      end
    end

    context 'attempting to connect to a non-tls server with tls' do
      require_no_tls
      # The exception raised is SocketTimeout on 3.6 server for whatever reason,
      # run the test on 4.0+ only.
      min_server_fcv '4.0'

      let(:options) { {ssl: true} }

      it 'reports host, port and tls status' do
        begin
          connection.connect!
        rescue Mongo::Error::SocketError => exc
        end
        expect(exc).not_to be nil
        expect(exc.message).to include('OpenSSL::SSL::SSLError')
        expect(exc.message).to include(server.address.to_s)
        expect(exc.message).to include('TLS')
        expect(exc.message).not_to include('no TLS')
      end
    end

    context 'attempting to connect to a tls server without tls' do
      require_tls

      let(:options) { {ssl: false} }

      it 'reports host, port and tls status' do
        begin
          connection.connect!
        rescue Mongo::Error::SocketError => exc
        end
        expect(exc).not_to be nil
        expect(exc.message).not_to include('OpenSSL::SSL::SSLError')
        addresses = Socket.getaddrinfo(server.address.host, nil)
        expect(addresses.any? do |address|
          exc.message.include?("#{address[2]}:#{server.address.port}")
        end).to be true
        expect(exc.message).to include('no TLS')
      end
    end
  end

  shared_examples_for 'caches client key' do
    it 'caches' do
      client.close
      Mongo::Auth::CredentialCache.clear

      RSpec::Mocks.with_temporary_scope do
        expect_any_instance_of(conversation_class).to receive(:hi).exactly(:once).and_call_original

        client.reconnect
        server = client.cluster.next_primary
        server.with_connection do
          server.with_connection do
            # nothing
          end
        end
      end
    end
  end

  describe 'scram-sha-1 client key caching' do
    clean_slate
    min_server_version '3.0'
    require_no_external_user

    let(:client) { authorized_client.with(max_pool_size: 2, auth_mech: :scram) }
    let(:conversation_class) { Mongo::Auth::Scram::Conversation }

    it_behaves_like 'caches client key'
  end

  describe 'scram-sha-256 client key caching' do
    clean_slate
    min_server_version '4.0'
    require_no_external_user

    let(:client) { authorized_client.with(max_pool_size: 2, auth_mech: :scram256) }
    let(:conversation_class) { Mongo::Auth::Scram256::Conversation }

    it_behaves_like 'caches client key'
  end

  context 'when only auth source is specified' do
    require_no_auth

    let(:client) do
      new_local_client(SpecConfig.instance.addresses, SpecConfig.instance.monitoring_options.merge(
        auth_source: 'foo'))
    end

    it 'does not authenticate' do
      expect(Mongo::Auth::User).not_to receive(:new)
      client.database.command(ping: 1)
    end
  end

  context 'when only auth mechanism is specified' do
    require_x509_auth

    let(:client) do
      new_local_client(SpecConfig.instance.addresses, base_options.merge(
        auth_mech: :mongodb_x509))
    end

    it 'authenticates' do
      expect(Mongo::Auth::User).to receive(:new).and_call_original
      client.database.command(ping: 1)
    end
  end

  context 'in lb topology' do
    require_topology :load_balanced

    context 'when authentication fails with network error' do
      let(:server) do
        authorized_client.cluster.next_primary
      end

      let(:base_options) do
        SpecConfig.instance.monitoring_options.merge(connect: SpecConfig.instance.test_options[:connect])
      end

      let(:connection) do
        Mongo::Server::Connection.new(server, base_options)
      end

      it 'includes service id in exception' do
        expect_any_instance_of(Mongo::Server::PendingConnection).to receive(:authenticate!).and_raise(Mongo::Error::SocketError)

        begin
          connection.connect!
        rescue Mongo::Error::SocketError => exc
          exc.service_id.should_not be nil
        else
          fail 'Expected the SocketError to be raised'
        end
      end
    end
  end
end