Skip to content

Commit 94088b4

Browse files
p-mongop
andcommitted
Fix RUBY-2055 Driver sends null pwd field in createUser when password is not specified (#1609)
* Shorten lines to 79 columns * RUBY-2055 fix user creation without password * Use dashes for second level headings * Give an example for creating an x.509 user Co-authored-by: Oleg Pudeyev <p@users.noreply.github.com>
1 parent 8ad1ea4 commit 94088b4

File tree

6 files changed

+157
-63
lines changed

6 files changed

+157
-63
lines changed

docs/tutorials/ruby-driver-authentication.txt

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,8 @@ Alternatively, setting the current database and credentials can be done in one s
5555
user:'test',
5656
password:'123' )
5757

58+
.. _auth-source:
59+
5860
Auth Source
5961
```````````
6062

docs/tutorials/user-management.txt

Lines changed: 99 additions & 61 deletions
Original file line numberDiff line numberDiff line change
@@ -12,18 +12,21 @@ User Management
1212
:depth: 1
1313
:class: singlecol
1414

15-
The Mongo Ruby Driver provides a set of methods for managing users in a MongoDB deployment.
16-
All of these methods are defined on the ``Mongo::Auth::User::View`` class, which defines the
17-
behavior for performing user-related operations on a database. You can access a database's
18-
user view by calling the ``users`` method on the correpsonding ``Mongo::Database`` object:
15+
The Mongo Ruby Driver provides a set of methods for managing users in a
16+
MongoDB deployment. All of these methods are defined on the
17+
``Mongo::Auth::User::View`` class, which defines the behavior for
18+
performing user-related operations on a database. You can access a database's
19+
user view by calling the ``users`` method on the correpsonding
20+
``Mongo::Database`` object:
1921

2022
.. code-block:: ruby
2123

2224
client.database.users
2325

24-
Note that this will open a view on the database to which the client is already connected.
25-
To interact with the users defined on a different database, call the client's ``use`` method
26-
and pass in the name of the database with which you want to connect:
26+
Note that this will open a view on the database to which the client is already
27+
connected. To interact with the users defined on a different database, call
28+
the client's ``use`` method and pass in the name of the database with which
29+
you want to connect:
2730

2831
.. code-block:: ruby
2932

@@ -34,12 +37,39 @@ In this example, all operations would be performed on the ``users`` database.
3437
For more information about users and user management, see MongoDB's
3538
:manual:`online documentation </core/security-users>`.
3639

37-
Creating a user
38-
```````````````
40+
41+
Users and Databases
42+
-------------------
43+
44+
When a client connects to the server, MongoDB distinguishes the database
45+
that the client will perform operations on from the `auth source <auth-source>`_
46+
which is the database storing the user that the client is authenticating as.
47+
48+
In many cases, the auth source is the same as the database. When they differ,
49+
user management operations must be done on the auth source database. For
50+
example, to create a user authenticating with X.509 certifcate, which must be
51+
defined on the ``$external`` database:
52+
53+
.. code-block:: ruby
54+
55+
client.use('$external').database.users.create(
56+
'C=US,ST=New York,L=New York City,O=MongoDB,OU=x509,CN=localhost',
57+
roles: [{role: 'read', db: 'admin'}],
58+
)
59+
60+
Note that the auth source is not specified for creating the user - auth source
61+
is only used during the authentication process. If ``#create`` is invoked with
62+
a ``User`` object with ``auth_source`` set, the auth source is ignored for
63+
the purposes of user management.
64+
65+
66+
Creating Users
67+
--------------
68+
3969
There are two ways to create a new database user with the Ruby Driver.
4070

41-
The simplest way to create a new user is to use the ``create`` method, passing in a username,
42-
password, and roles:
71+
The simplest way to create a new user is to use the ``create`` method,
72+
passing in a username, password, and roles:
4373

4474
.. code-block:: ruby
4575

@@ -49,32 +79,34 @@ password, and roles:
4979
roles: [ Mongo::Auth::Roles::READWRITE ]
5080
)
5181

52-
Another way to create a user is to first create a ``Mongo::Auth::User`` object with all the
53-
user information and then pass that object into the ``create`` method instead.
82+
Another way to create a user is to first create a ``Mongo::Auth::User`` object
83+
with all the user information and then pass that object into the ``create``
84+
method instead.
5485

5586
.. code-block:: ruby
5687

57-
user = Mongo::User.new({
88+
user = Mongo::User.new(
5889
user: 'alanturing',
5990
password: 'enigma',
6091
roles: [ Mongo::Auth::Roles::READWRITE ]
61-
})
92+
)
6293

6394
client.database.users.create(user)
6495

65-
Note that your new user's credentials will be stored in whatever database your ``client``
66-
object is currently connected to. This will be your user's ``auth_source``, and you must
67-
be connected to that same database in order to update, remove, or get information about
68-
the user you just created in the future.
96+
Note that your new user's credentials will be stored in whatever database your
97+
``client`` object is currently connected to. This will be your user's
98+
``auth_source``, and you must be connected to that same database in order to
99+
update, remove, or get information about the user you just created in the future.
69100

70101
The ``create`` method takes a ``Hash`` of options as an optional second argument.
71102
The ``:roles`` option allows you to grant permissions to the new user.
72-
For example, the ``Mongo::Auth::Roles::READ_WRITE`` role grants the user the ability to both
73-
read from and write to the database in which they were created. Each role can be specified
74-
as a ``String`` or as a ``Hash``. If you would like to grant permissions to a user on a database
75-
other than the one on which they were created, you can pass that database name in the role ``Hash``.
76-
To create a user ``alanturing`` with permission to read and write on the ``machines`` database,
77-
you could execute the following code:
103+
For example, the ``Mongo::Auth::Roles::READ_WRITE`` role grants the user the
104+
ability to both read from and write to the database in which they were created.
105+
Each role can be specified as a ``String`` or as a ``Hash``. If you would like
106+
to grant permissions to a user on a database other than the one on which they
107+
were created, you can pass that database name in the role ``Hash``. To create
108+
a user ``alanturing`` with permission to read and write on the ``machines``
109+
database, you could execute the following code:
78110

79111
.. code-block:: ruby
80112

@@ -84,54 +116,59 @@ you could execute the following code:
84116
roles: [{ role: Mongo::Auth::Roles::READWRITE, db: 'machines' }]
85117
)
86118

87-
For more information about roles in MongoDB, see the :manual:`Built-in roles</reference/built-in-roles/>`
88-
documentation.
119+
For more information about roles in MongoDB, see the
120+
:manual:`Built-in roles</reference/built-in-roles/>` documentation.
89121

90-
In addition to the ``:roles`` option, the ``create`` method supports a ``:session`` option,
91-
which allows you to specify a ``Mongo::Session`` object to use for this operation,
92-
as well as a ``:write_concern`` option, which specifies the write concern of this operation
93-
when performed on a replica set.
122+
In addition to the ``:roles`` option, the ``create`` method supports a
123+
``:session`` option, which allows you to specify a ``Mongo::Session`` object
124+
to use for this operation, as well as a ``:write_concern`` option,
125+
which specifies the write concern of this operation when performed on a
126+
replica set.
94127

95128
.. seealso::
96129
:manual:`Built-in roles</reference/built-in-roles/>`
97130
:manual:`Write Concerns</core/replica-set-write-concern/>`,
98131
:ref:`Sessions<sessions>`,
99132

100-
User Info
101-
`````````
102-
To view information about a user that already exists in the database, use the ``info`` method:
133+
134+
User Information
135+
----------------
136+
137+
To view information about a user that already exists in the database, use the
138+
``info`` method:
103139

104140
.. code-block:: ruby
105141

106142
client.database.users.info('alanturing')
107143

108-
If the user exists, this method will return an ``Array`` object
109-
containing a ``Hash`` with information about the user, such as
110-
their id, username, the database they were created on, and their
111-
roles. If the user doesn't exist, this method will return an
112-
empty Array.
144+
If the user exists, this method will return an ``Array`` object containing a
145+
``Hash`` with information about the user, such as their id, username, the
146+
database they were created on, and their roles. If the user doesn't exist,
147+
this method will return an empty Array.
113148

114-
The ``info`` method also takes an optional ``Hash`` of options
115-
as a second argument. Currently, the only supported option is ``:session``,
116-
which allows you to specify a ``Mongo::Session`` object to use for this
117-
operation.
149+
The ``info`` method also takes an optional ``Hash`` of options as a second
150+
argument. Currently, the only supported option is ``:session``, which allows
151+
you to specify a ``Mongo::Session`` object to use for this operation.
118152

119-
The Ruby Driver does not have a method that lists all of the users
120-
that currently exist in a database.
153+
The Ruby Driver does not have a method that lists all of the users that
154+
currently exist in a database.
121155

122156
.. seealso::
123157
:ref:`Sessions <sessions>`
124158

125-
Updating a user
126-
```````````````
127-
To update a user that already exists in the database, you can use the ``update`` method in
128-
one of two ways. The first way is to specify the name of the user you wish to update,
129-
along with a new set of options.
159+
160+
Updating Users
161+
--------------
162+
163+
To update a user that already exists in the database, you can use the
164+
``update`` method in one of two ways. The first way is to specify the name of
165+
the user you wish to update, along with a new set of options.
130166

131167
.. warning::
132168

133-
You must include all user options in the options ``Hash``, even those options whose values
134-
will remain the same. Omitting an option is the same as setting it to an empty value.
169+
You must include all user options in the options ``Hash``, even those options
170+
whose values will remain the same. Omitting an option is the same as setting
171+
it to an empty value.
135172

136173
.. code-block:: ruby
137174

@@ -141,8 +178,8 @@ along with a new set of options.
141178
password: 'turing-test'
142179
)
143180

144-
The second way to update a user is to pass an updated ``Mongo::Auth::User`` object
145-
to the ``update`` method in lieu of a username.
181+
The second way to update a user is to pass an updated ``Mongo::Auth::User``
182+
object to the ``update`` method in lieu of a username.
146183

147184
.. code-block:: ruby
148185

@@ -154,17 +191,19 @@ to the ``update`` method in lieu of a username.
154191

155192
client.database.users.update(user)
156193

157-
Optionally, the ``update`` method takes a ``Hash`` of options as a second argument.
158-
The two possible options for this method are ``:session``, which allows you to specify
159-
a ``Mongo::Session`` object on which to perform this operation, and ``:write_concern``,
160-
which sets a write concern if this operation is performed on a replica set.
194+
Optionally, the ``update`` method takes a ``Hash`` of options as a second
195+
argument. The two possible options for this method are ``:session``, which
196+
allows you to specify a ``Mongo::Session`` object on which to perform this
197+
operation, and ``:write_concern``, which sets a write concern if this operation
198+
is performed on a replica set.
161199

162200
.. seealso::
163201
:ref:`Sessions<sessions>`
164202
:manual:`Write Concerns</core/replica-set-write-concern/>`,
165203

166-
Removing users
167-
``````````````
204+
Removing Users
205+
--------------
206+
168207
To remove a user from the database, use the ``remove`` method:
169208

170209
.. code-block:: ruby
@@ -183,4 +222,3 @@ from a database.
183222
.. seealso::
184223
:ref:`Sessions<sessions>`
185224
:manual:`Write Concerns</core/replica-set-write-concern/>`,
186-

lib/mongo/auth/user.rb

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,8 @@ def sasl_prepped_password
151151
# authorized for.
152152
# @option options [ String ] :user The user name.
153153
# @option options [ String ] :password The user's password.
154+
# @option options [ String ] :pwd Legacy option for the user's password.
155+
# If :password and :pwd are both specified, :password takes precedence.
154156
# @option options [ Symbol ] :auth_mech The authorization mechanism.
155157
# @option options [ Array<String>, Array<Hash> ] roles The user roles.
156158
# @option options [ String ] :client_key The user's client key cached from a previous
@@ -196,7 +198,11 @@ def initialize(options)
196198
#
197199
# @since 2.0.0
198200
def spec
199-
{ pwd: password, roles: roles }
201+
{roles: roles}.tap do |spec|
202+
if password
203+
spec[:pwd] = password
204+
end
205+
end
200206
end
201207

202208
private

spec/lite_spec_helper.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -127,6 +127,7 @@
127127
end
128128

129129
config.expect_with :rspec do |c|
130+
c.syntax = [:should, :expect]
130131
c.max_formatted_output_length = 10000
131132
end
132133
end

spec/mongo/auth/user/view_spec.rb

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,10 @@
22

33
describe Mongo::Auth::User::View do
44

5+
let(:database) { root_authorized_client.database }
6+
57
let(:view) do
6-
described_class.new(root_authorized_client.database)
8+
described_class.new(database)
79
end
810

911
before do
@@ -51,6 +53,39 @@
5153

5254
describe '#create' do
5355

56+
context 'when password is not provided' do
57+
58+
let(:database) { root_authorized_client.use('$external').database }
59+
60+
let(:username) { 'passwordless-user' }
61+
62+
let(:response) do
63+
view.create(
64+
username,
65+
# https://stackoverflow.com/questions/55939832/mongodb-external-database-cannot-create-new-user-with-user-defined-role
66+
roles: [{role: 'read', db: 'admin'}],
67+
)
68+
end
69+
70+
before do
71+
begin
72+
view.remove(username)
73+
rescue Mongo::Error::OperationFailure
74+
# can be user not found, ignore
75+
end
76+
end
77+
78+
it 'creates the user' do
79+
view.info(username).should == []
80+
81+
lambda do
82+
response
83+
end.should_not raise_error
84+
85+
view.info(username).first['user'].should == username
86+
end
87+
end
88+
5489
context 'when a session is not used' do
5590

5691
let!(:response) do

spec/mongo/auth/user_spec.rb

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -325,4 +325,16 @@
325325
end
326326
end
327327
end
328+
329+
describe '#spec' do
330+
context 'when no password and no roles are set' do
331+
let(:user) do
332+
described_class.new(user: 'foo')
333+
end
334+
335+
it 'is a hash with empty roles' do
336+
user.spec.should == {roles: []}
337+
end
338+
end
339+
end
328340
end

0 commit comments

Comments
 (0)