Search code examples
pythonflaskflask-loginflask-security

Flask-Login trying to change my user.id into random strings instead of established ints?


Since updating my Flask app's dependencies, my Sentry is logging errors I don't understand. Changes to Flask-Login or Flask-Security-Too have thrown me off.

My user model has always used an Integer as its primary_key

class User(db.Model, UserMixin):

    id = db.Column(db.Integer, primary_key=True)

I'm getting the following error:

(psycopg2.errors.InvalidTextRepresentation) invalid input syntax for integer: "5818f9aad20447b1b6a37c51f2c2xxxx"
LINE 3: WHERE "user".id = '5818f9aad20447b1b6a37c51f2c2xxxx' 

Which I assume stems from Flask-Security-Too's datastore user lookup:

def find_user(self, **kwargs):
    return self.user_model.query.filter_by(**kwargs).first()

But maybe the problem goes bath further to Flask-Login as it's getting this random ID from the session:

    # Load user from Flask Session
    user_id = session.get("_user_id")
    if user_id is not None and self._user_callback is not None:
        user = self._user_callback(user_id)

Is Flask-Security-Too's fs_uniquifier in my session? Is that what's stepping into the place of my integer ID? I'm hoping I can avoid refactoring my entire code and database and retain my user.id as an int. Any thoughts are appreciated.

Here is the full trace that has occurred 15 times since upgrading, all without halting users from accessing or submitting data:

InvalidTextRepresentation: invalid input syntax for integer: "63b93cd01b8f4e65a7daa83becc3xxxx"
LINE 3: WHERE "user".id = '63b93cd01b8f4e65a7daa83becc3xxxx' 
                          ^

  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
DataError: (psycopg2.errors.InvalidTextRepresentation) invalid input syntax for integer: "63b93cd01b8f4e65a7daa83becc3xxxx"
LINE 3: WHERE "user".id = '63b93cd01b8f4e65a7daa83becc3xxxx' 
                          ^

[SQL: SELECT "user".id AS user_id, "user".school_id AS user_school_id, "user".location_id AS user_location_id, "user".first_name AS user_first_name, "user".last_name AS user_last_name, "user".display_name AS user_display_name, "user".image AS user_image, "user".email AS user_email, "user".password AS use...
  File "flask/app.py", line 2463, in wsgi_app
    response = self.full_dispatch_request()
  File "flask/app.py", line 1760, in full_dispatch_request
    rv = self.handle_user_exception(e)
  File "__init__.py", line 271, in error_router
    return original_handler(e)
  File "flask/app.py", line 1756, in full_dispatch_request
    rv = self.preprocess_request()
  File "flask/app.py", line 2247, in preprocess_request
    rv = self.ensure_sync(before_func)()
  File "flask_principal.py", line 477, in _on_before_request
    identity = loader()
  File "flask_security/core.py", line 245, in _identity_loader
    if not isinstance(current_user._get_current_object(), AnonymousUserMixin):
  File "werkzeug/local.py", line 516, in _get_current_object
    return get_name(local())  # type: ignore
  File "flask_login/utils.py", line 25, in <lambda>
    current_user = LocalProxy(lambda: _get_user())
  File "flask_login/utils.py", line 372, in _get_user
    current_app.login_manager._load_user()
  File "flask_login/login_manager.py", line 364, in _load_user
    user = self._user_callback(user_id)
  File "flask_security/core.py", line 221, in _user_loader
    return _security.datastore.find_user(id=user_id)
  File "flask_security/datastore.py", line 254, in find_user
    return self.user_model.query.filter_by(**kwargs).first()
  File "sqlalchemy/orm/query.py", line 2819, in first
    return self.limit(1)._iter().first()
  File "sqlalchemy/orm/query.py", line 2903, in _iter
    result = self.session.execute(
  File "sqlalchemy/orm/session.py", line 1712, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "sqlalchemy/engine/base.py", line 1631, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "sqlalchemy/sql/elements.py", line 332, in _execute_on_connection
    return connection._execute_clauseelement(
  File "sqlalchemy/engine/base.py", line 1498, in _execute_clauseelement
    ret = self._execute_context(
  File "sqlalchemy/engine/base.py", line 1862, in _execute_context
    self._handle_dbapi_exception(
  File "sqlalchemy/engine/base.py", line 2043, in _handle_dbapi_exception
    util.raise_(
  File "sqlalchemy/util/compat.py", line 208, in raise_
    raise exception
  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
InFailedSqlTransaction: current transaction is aborted, commands ignored until end of transaction block

  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)
InternalError: (psycopg2.errors.InFailedSqlTransaction) current transaction is aborted, commands ignored until end of transaction block

[SQL: SELECT "user".id AS user_id, "user".school_id AS user_school_id, "user".location_id AS user_location_id, "user".first_name AS user_first_name, "user".last_name AS user_last_name, "user".display_name AS user_display_name, "user".image AS user_image, "user".email AS user_email, "user".password AS user_password, "user".fs_uniquifier AS user_fs_uniquifier, "user".phone AS user_phone...
  File "flask/app.py", line 2486, in __call__
    return self.wsgi_app(environ, start_response)
  File "flask/app.py", line 2466, in wsgi_app
    response = self.handle_exception(e)
  File "__init__.py", line 271, in error_router
    return original_handler(e)
  File "flask/app.py", line 1662, in handle_exception
    server_error = self.ensure_sync(handler)(server_error)
  File "__init__.py", line 21, in crash_page
    return render_template('error_pages/500.html'), 500
  File "flask/templating.py", line 147, in render_template
    return _render(app, template, context)
  File "flask/templating.py", line 128, in _render
    app.update_template_context(context)
  File "flask/app.py", line 932, in update_template_context
    context.update(func())
  File "flask_login/utils.py", line 407, in _user_context_processor
    return dict(current_user=_get_user())
  File "flask_login/utils.py", line 372, in _get_user
    current_app.login_manager._load_user()
  File "flask_login/login_manager.py", line 364, in _load_user
    user = self._user_callback(user_id)
  File "flask_security/core.py", line 221, in _user_loader
    return _security.datastore.find_user(id=user_id)
  File "flask_security/datastore.py", line 254, in find_user
    return self.user_model.query.filter_by(**kwargs).first()
  File "sqlalchemy/orm/query.py", line 2819, in first
    return self.limit(1)._iter().first()
  File "sqlalchemy/orm/query.py", line 2903, in _iter
    result = self.session.execute(
  File "sqlalchemy/orm/session.py", line 1712, in execute
    result = conn._execute_20(statement, params or {}, execution_options)
  File "sqlalchemy/engine/base.py", line 1631, in _execute_20
    return meth(self, args_10style, kwargs_10style, execution_options)
  File "sqlalchemy/sql/elements.py", line 332, in _execute_on_connection
    return connection._execute_clauseelement(
  File "sqlalchemy/engine/base.py", line 1498, in _execute_clauseelement
    ret = self._execute_context(
  File "sqlalchemy/engine/base.py", line 1862, in _execute_context
    self._handle_dbapi_exception(
  File "sqlalchemy/engine/base.py", line 2043, in _handle_dbapi_exception
    util.raise_(
  File "sqlalchemy/util/compat.py", line 208, in raise_
    raise exception
  File "sqlalchemy/engine/base.py", line 1819, in _execute_context
    self.dialect.do_execute(
  File "sqlalchemy/engine/default.py", line 732, in do_execute
    cursor.execute(statement, parameters)

Solution

  • Some additional info - as of FS2 3.4.0 it no longer references user.id at all - so that can be whatever you want. FS2 overrides flask_login's UserMixin::get_id - and returns the value of fs_uniquifier. And yes - that is what is stored in the session cookie - and no - nothing in the session cookie is encrypted (it is signed). So: 1) are you overriding get_id()? 2) any chance that since it seems to be 'working' that you have some post-processing - maybe in a signal or something and it is calling the find_user or some other DB query?