MongoEngine / MongoEngine/mongoengine

Inconsistent behavior of auth options in host string and kwarg when connect

Open
#2,562 0 comments 0 reactions 0 assignees View on GitHub

Nobody has claimed this yet.

Dominant language
Python
Stars
4.3k
Forks
1.2k
Avg merge
4h 41m
Merged PRs (30d)
11

Description

Background

MongoDB version: 4.2.6
mongoengine version: 0.23.1
pymongo: 3.12.0

In my scenario, I need to connect with authentication_source and authentication_mechanism and then do some queries.

My code goes like this,

class User(Document):
    name = StringField()

# connect
connect(
    host='mongodb://{username}:{password}@{host}:{port}/{db}'.format(**MONGO_CONFIG),
    authentication_mechanism=MONGO_CONFIG['mechanism'],
    authentication_source=MONGO_CONFIG['db']
)

# query
User.objects.count()

Analysis

1. If auth options to be a kwarg of connect

authentication_mechanism and authentication_source will be cleaned when _create_connection,

https://github.com/MongoEngine/mongoengine/blob/5fe9436ea7bd2f47812b017104285210848a1276/mongoengine/connection.py#L265-L275

And will get MongoCredential in pymongo like this when connect,

MongoCredential(mechanism='DEFAULT', source='s', username='u', password='p', mechanism_properties=None, cache=<pymongo.auth._Cache object at 0x7f9aa2db0e50>)

The error will be raised when we do a query or a count op, because the new MongoCredential is different

Traceback (most recent call last):
  File "debug.py", line 27, in <module>
    User.objects.count()
  File "/Users/.../opt/anaconda3/envs/mongoengine-pr/lib/python3.7/site-packages/mongoengine/queryset/manager.py", line 38, in __get__
    queryset = queryset_class(owner, owner._get_collection())
  File "/Users/.../opt/anaconda3/envs/mongoengine-pr/lib/python3.7/site-packages/mongoengine/document.py", line 215, in _get_collection
    db = cls._get_db()
  File "/Users/.../opt/anaconda3/envs/mongoengine-pr/lib/python3.7/site-packages/mongoengine/document.py", line 193, in _get_db
    return get_db(cls._meta.get("db_alias", DEFAULT_CONNECTION_NAME))
  File "/Users/.../opt/anaconda3/envs/mongoengine-pr/lib/python3.7/site-packages/mongoengine/connection.py", line 368, in get_db
    conn_settings["username"], conn_settings["password"], **auth_kwargs
  File "/Users/.../opt/anaconda3/envs/mongoengine-pr/lib/python3.7/site-packages/pymongo/database.py", line 1575, in authenticate
    connect=True)
  File "/Users/.../opt/anaconda3/envs/mongoengine-pr/lib/python3.7/site-packages/pymongo/mongo_client.py", line 806, in _cache_credentials
    raise OperationFailure('Another user is already authenticated '
pymongo.errors.OperationFailure: Another user is already authenticated to this database. You must logout first.
MongoCredential(mechanism='SCRAM-SHA-256', source='s', username='u', password='p', mechanism_properties=None, cache=<pymongo.auth._Cache object at 0x7f9fe769b390>)
authentication_mechanism

The root cause of this issue is that the get_db function is handling the transform (mongoengine's authentication_mechanism to pymongo's mechanism) when a query is taking off. However, that is not happening during the connection phase.

https://github.com/MongoEngine/mongoengine/blob/5fe9436ea7bd2f47812b017104285210848a1276/mongoengine/connection.py#L356

authentication_source

authentication_source is not handling during the connection phase too. But this will be set as db name by default in pymongo.

2. If auth options to be in the host string

Everything goes fine in this situation because pymongo will parse the host string and set them correctly. Like,

connect(host='mongodb://{username}:{password}@{host}:{port}/{db}?authMechanism={mechanism}&authSource={db}'.format(**MONGO_CONFIG))

https://github.com/mongodb/mongo-python-driver/blob/2eb0df812c6a4afbdbcd12692ca8da0b4dd0c14e/pymongo/mongo_client.py#L659

So should we reconcile them?

Contributor guide

Open the contributing guide

First steps

  1. Read the whole issue, then the project's contributing guide.
  2. Comment on the issue to say you are picking it up — it saves two people doing the same work.
  3. Fork the repository and make your change on a branch.
  4. Open a pull request that references the issue number.

Research direction

Start in mongoengine/connection.py, comparing _create_connection with get_db, and then review PyMongo's MongoClient option parsing at the linked entry point. Reproduce the shown connection and User.objects.count() flow with options in the host string and as kwargs; done means both forms produce consistent authentication behavior without the credential conflict.

Written by the indexing model from the issue text.

Assessment

Tech stack
mongodb, python
Domain
authentication, backend, databases
Issue type
Bug
Difficulty
4/5
Estimated time
3-5 days
Activity status
Stale
Clarity
Mostly clear
Newbie friendliness
35/100

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.