Tornado authenticating to MongoDB using Motor

1.2k Views Asked by At

I made a tornado app that is supposed to run some queries against a mongodb instance running on another machine. To this purpose I have set up mongodb with authentication and users. I have checked that everything works and that I can authenticate to tornado using the Robo 3T app and a small synchronous script that uses pymongo.

This is how I initialize my tornado application:

class API(tornado.web.Application):
    def __init__(self):
        settings = dict(
            autoreload=True,
            compiled_template_cache=False,
            static_hash_cache=False,
            serve_traceback=True,
            cookie_secret="secret",
            xsrf_cookies=True,
            static_path=os.path.join(os.path.dirname(__file__), "media"),
            template_loader=tornado.template.Loader('./templates')
        )

        mongohost = os.environ.get('MONGOHOST', 'localhost')
        mongoport = os.environ.get('MONGOPORT', 27017)
        mongouser = os.environ.get('MONGOUSER')
        mongopass = os.environ.get('MONGOPASS')
        mongodb = os.environ.get('MONGODB')
        mongouri = f'mongodb://{mongouser}:{mongopass}@{mongohost}:{mongoport}/{mongodb}'

        self.client = motor.motor_tornado.MotorClient(mongouri)
        logging.info('connected to mongodb')
        self.db = self.client.get_default_database()
        logging.info('got mongodb database')
        tornado.web.Application.__init__(self, url_patterns, **settings)


def main():
    port = 8888
    FORMAT = ('%(asctime)s %(levelname) -10s %(name) -30s %(funcName) -35s %(lineno) -5d: %(message)s')
    logging.basicConfig(level=logging.DEBUG, format=FORMAT)

    tornado.ioloop.IOLoop.configure(TornadoUvloop)
    app = API()
    app.listen(port)
    signal.signal(signal.SIGINT, sig_exit)
    logging.info('Tornado server started on port %s' % port)
    tornado.ioloop.IOLoop.instance().start()


if __name__ == "__main__":
    main()

Everything appears to run until one of my handlers that actually performs queries against the database is hit. Code from the handler looks like this:

      cursor = self.application.db['events'].find(
          find,
          projection
      ).limit(perpage).skip(page*perpage)
      buffsize = 0
      try:
          while (yield cursor.fetch_next):
              message = cursor.next_object()
              self.write(json.dumps(message, default=json_util.default))
              buffsize += 1
              if buffsize >= 10:
                  buffsize = 0
                  yield self.flush()
          yield self.flush()
      except Exception:
          logging.error('Could not connect to mongodb', exc_info=True)

This code worked just fine before trying to use authentication but now it raises exceptions and stoped working:

017-12-12 13:00:20,718 INFO       root                           __init__                             37  : connected to mongodb
2017-12-12 13:00:20,718 INFO       root                           __init__                             39  : got mongodb database
2017-12-12 13:00:20,723 INFO       root                           main                                 67  : Tornado server started on port 8888
2017-12-12 13:00:25,226 INFO       tornado.general                _check_file                          198 : /Users/liviu/Documents/Work/api_v2/src/api_tmp/handlers/event_list.py modified; restarting server
2017-12-12 13:00:25,469 INFO       root                           __init__                             37  : connected to mongodb
2017-12-12 13:00:25,469 INFO       root                           __init__                             39  : got mongodb database
2017-12-12 13:00:25,472 INFO       root                           main                                 67  : Tornado server started on port 8888
2017-12-12 13:00:28,152 INFO       root                           get                                  266 : now querying database
2017-12-12 13:00:28,214 ERROR      root                           get                                  355 : Could not connect to mongodb
Traceback (most recent call last):
  File "/Users/liviu/Documents/Work/api_v2/src/api_tmp/handlers/event_list.py", line 346, in get
    while (yield cursor.fetch_next):
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/tornado/gen.py", line 1055, in run
    value = future.result()
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/tornado/concurrent.py", line 238, in result
    raise_exc_info(self._exc_info)
  File "<string>", line 4, in raise_exc_info
  File "/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/concurrent/futures/thread.py", line 56, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/cursor.py", line 1055, in _refresh
    self.__collation))
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/cursor.py", line 892, in __send_message
    **kwargs)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/mongo_client.py", line 950, in _send_message_with_response
    exhaust)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/mongo_client.py", line 961, in _reset_on_error
    return func(*args, **kwargs)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/server.py", line 99, in send_message_with_response
    with self.get_socket(all_credentials, exhaust) as sock_info:
  File "/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/server.py", line 168, in get_socket
    with self.pool.get_socket(all_credentials, checkout) as sock_info:
  File "/usr/local/Cellar/python3/3.6.3/Frameworks/Python.framework/Versions/3.6/lib/python3.6/contextlib.py", line 81, in __enter__
    return next(self.gen)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/pool.py", line 852, in get_socket
    sock_info.check_auth(all_credentials)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/pool.py", line 570, in check_auth
    auth.authenticate(credentials, self)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/auth.py", line 486, in authenticate
    auth_func(credentials, sock_info)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/auth.py", line 466, in _authenticate_default
    return _authenticate_scram_sha1(credentials, sock_info)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/auth.py", line 209, in _authenticate_scram_sha1
    res = sock_info.command(source, cmd)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/pool.py", line 477, in command
    collation=collation)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/network.py", line 116, in command
    parse_write_concern_error=parse_write_concern_error)
  File "/Users/liviu/.venv/api/lib/python3.6/site-packages/pymongo/helpers.py", line 210, in _check_command_response
    raise OperationFailure(msg % errmsg, code, response)
pymongo.errors.OperationFailure: Authentication failed.
2017-12-12 13:00:28,220 INFO       tornado.access                 log_request                          2063: 200 GET /event/list/web?starttime=2017-01-01&endtime=2017-02-03T14:00:00&minlatitude=10&maxlatitude=20&minlongitude=10&maxlongitude=20&minmagnitude=2&maxmagnitude=5&mindepth=10&maxdepth=100 (::1) 67.88ms

I was able to find some simple examples on how to log in and authenticate to MongoDB and as far as I can figure, that is exactly how I am also doing it (https://github.com/mongodb/motor/blob/master/doc/examples/authentication.rst).

Can anybody shed some light on what is going on and how to properly authenticate to MongoDB from an actual working tornado application using Motor?

P.S. I am using Python 3.6, tornado 4.5.2 and motor 1.1.

P.P.S. In the meantime I have discovered that using this as the uri makes it work properly:

mongouri = f'mongodb://{mongouser}:{mongopass}@{mongohost}:{mongoport}/admin'

In the above, I replaced {mongodb} with the "admin" db. After the client gets connected and authenticated on the admin database, I can proceed to get_database(mongodb) and it will work properly. If anyone cares to more clearly articulate what is going on I will accept the answer.

1

There are 1 best solutions below

0
Gnurfos On

From the mongodb docs :

authSource

Specify the database name associated with the user’s credentials. authSource defaults to the database specified in the connection string.

That explains why it works if you specify "admin" as target database. So in your case you should use:

mongouri = f'mongodb://{mongouser}:{mongopass}@{mongohost}:{mongoport}/{mongodb}?authSource=admin'