Repository: LMFDB/lmfdb

Issue:
flask-login version
We use a fixed version of flask-login, should we now allow to update it? 
If we do this, everybody would need to update their version of flask-login


Discussion:
There would have been a reason for setting the fixed version number in requirements.txt, probably that at the time, the most recent version broke our code.  Have you tested the most recent version?  Were any code changes needed?

Yes, there will be some minor changes (for user objects, user.is_anonymous used to be a method, but is now a value, so we currently have user.is_anonymous() and would change it to user.is_anonymous -- there are one or two similar changes to the code).  I think at the time, there was concern about making people update their version of flask login.  If we do it now, everyone will have to update.  But, updating it is easy, so forcing an old version seems odd.
I suggest wrapping the small number (I think) of places where the interface has changed with a test for the version number.  We have done this before.  That way we can have code which works with new installations using the newest flask_login without breaking existing ones.
The relevant functions are (as I just tried out):
sage: import flask_login
sage: flask_login.__version__
'0.2.11'
sage: flask_login.__version_info__
('0', '2', '11')

I just tried the commands above with sage on lehner and even after importing flask_login, flask_login.version and flask_login.version_info both give errors.
try   `flask_login.__version__`

this is the only one working for me (JEC comment was processed by Markdown so `__version__` looks like __version__)
@jwj61 we are the victim of github's markup!  After "import flask_version" you type flask_login DOT DOUBLE-UNDESCORE version DOUBLE-UNDERSCORE.  In my and @sanni85 's posts that was converted into a bold face....
Thanks.  That helps, but the newest version of flask_login does not seem to have this either.

Resolving patch:
diff --git a/lmfdb/users/__init__.py b/lmfdb/users/__init__.py
--- a/lmfdb/users/__init__.py
+++ b/lmfdb/users/__init__.py
@@ -1,11 +1,16 @@
 # -*- coding: utf-8 -*-
 
-from main import login_page, login_manager, admin_required, housekeeping
+from main import (login_page, login_manager, admin_required,
+                  housekeeping, FLASK_LOGIN_VERSION, FLASK_LOGIN_LIMIT)
 assert admin_required # silence pyflakes
 assert housekeeping # silence pyflakes
 
 from lmfdb.base import app
 
+from lmfdb.utils import make_logger
+from distutils.version import StrictVersion
+
+
 # secret key, necessary for sessions, and sessions are
 # in turn necessary for users to login
 app.secret_key = '9af"]ßÄ!_°$2ha€42~µ…010'
@@ -13,3 +18,11 @@
 login_manager.init_app(app)
 
 app.register_blueprint(login_page, url_prefix="/users")
+
+users_logger = make_logger("users", hl=True)
+
+if StrictVersion(FLASK_LOGIN_VERSION) < StrictVersion(FLASK_LOGIN_LIMIT):
+    users_logger.warning("DEPRECATION-WARNING: flask-login is older than version {version}. "
+          "Versions older than {version} have different functionality and may stop working in the future. "
+          "Consider updating, perhaps through `sage -pip install flask-login`."
+          .format(version=FLASK_LOGIN_LIMIT))
diff --git a/lmfdb/users/main.py b/lmfdb/users/main.py
--- a/lmfdb/users/main.py
+++ b/lmfdb/users/main.py
@@ -11,6 +11,7 @@
 from bson.son import SON
 from flask import render_template, request, Blueprint, url_for, make_response
 from flask.ext.login import login_required, login_user, current_user, logout_user
+from distutils.version import StrictVersion
 
 login_page = Blueprint("users", __name__, template_folder='templates')
 import lmfdb.utils
@@ -22,6 +23,11 @@
 from flask.ext.login import LoginManager
 login_manager = LoginManager()
 
+# We log a warning if the version of flask-login is less than FLASK_LOGIN_LIMIT
+import flask.ext.login
+FLASK_LOGIN_VERSION = flask.ext.login.__version__
+FLASK_LOGIN_LIMIT = '0.3.0'
+
 import pwdmanager
 from pwdmanager import LmfdbUser, LmfdbAnonymousUser
 
@@ -50,7 +56,12 @@ def ctx_proc_userdata():
     userdata = {}
     userdata['userid'] = 'anon' if current_user.is_anonymous() else current_user._uid
     userdata['username'] = 'Anonymous' if current_user.is_anonymous() else current_user.name
-    userdata['user_is_authenticated'] = current_user.is_authenticated()
+
+    if StrictVersion(FLASK_LOGIN_VERSION) > StrictVersion(FLASK_LOGIN_LIMIT):
+        userdata['user_is_authenticated'] = current_user.is_authenticated
+    else:
+        userdata['user_is_authenticated'] = current_user.is_authenticated()
+
     try:
         roles = getDBConnection()['admin'].command(SON({"connectionStatus":int(1)}))
         userdata['user_can_write'] = 'readWrite' in [el['role'] for el in roles['authInfo']['authenticatedUserRoles'] if el['db']=='inventory']
diff --git a/lmfdb/users/pwdmanager.py b/lmfdb/users/pwdmanager.py
--- a/lmfdb/users/pwdmanager.py
+++ b/lmfdb/users/pwdmanager.py
@@ -12,7 +12,13 @@
 fixed_salt = '=tU\xfcn|\xab\x0b!\x08\xe3\x1d\xd8\xe8d\xb9\xcc\xc3fM\xe9O\xfb\x02\x9e\x00\x05`\xbb\xb9\xa7\x98'
 
 from lmfdb import base
-from main import logger
+from main import logger, FLASK_LOGIN_VERSION, FLASK_LOGIN_LIMIT
+from distutils.version import StrictVersion
+
+# Read about flask-login if you are unfamiliar with this UserMixin/Login
+from flask.ext.login import UserMixin
+from flask.ext.login import AnonymousUserMixin
+
 
 def get_users():
     return base.getDBConnection().userdb.users
@@ -62,10 +68,6 @@ def bchash(pwd, existing_hash = None):
         logger.warning("Failed to return bchash, perhaps bcrypt is not installed");
         return None
 
-# Read about flask-login if you are unfamiliar with this UserMixin/Login
-from flask.ext.login import UserMixin
-
-
 class LmfdbUser(UserMixin):
     """
     The User Object
@@ -137,7 +139,9 @@ def id(self):
 
     def is_anonymous(self):
         """required by flask-login user class"""
-        return not self.is_authenticated()
+        if StrictVersion(FLASK_LOGIN_VERSION) < StrictVersion(FLASK_LOGIN_LIMIT):
+            return not self.is_authenticated()
+        return not self.is_authenticated
 
     def is_admin(self):
         """true, iff has attribute admin set to True"""
@@ -234,9 +238,6 @@ def get_user_list():
         ret.append((e['_id'], name))
     return ret
 
-from flask.ext.login import AnonymousUserMixin
-
-
 class LmfdbAnonymousUser(AnonymousUserMixin):
     """
     The sole purpose of this Anonymous User is the 'is_admin' method
@@ -248,6 +249,12 @@ def is_admin(self):
     def name(self):
         return "Anonymous"
 
+    # For versions of flask_login earlier than 0.3.0,
+    # AnonymousUserMixin.is_anonymous() is callable. For later versions, it's a
+    # property. To match the behavior of LmfdbUser, we make it callable always.
+    def is_anonymous(self):
+        return True
+
 if __name__ == "__main__":
     print "Usage:"
     print "add user"


Test patch: