MongoEngine/mongoengine

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

ryantd opened this issue · 0 comments

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,

def _clean_settings(settings_dict):
irrelevant_fields_set = {
"name",
"username",
"password",
"authentication_source",
"authentication_mechanism",
}
return {
k: v for k, v in settings_dict.items() if k not in irrelevant_fields_set
}

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.

auth_kwargs["mechanism"] = conn_settings["authentication_mechanism"]

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?