Compatibility layer for plugins depending on user.is_anonymous() etc

Logs a deprecation warning when any of the made-property fields is
accessed as a getter and return value. Implements __eq__, __ne__,
__bool__ and __nonzero__ to allow things like "user.is_anonymous ==
True" or "not user.is_anonymous" is used.

Careful with serialization to json, needs "bool(...)" wrapper to not
cause an exception (or inclusion in a custom serializer).

Affected fields:

  * is_anonymous
  * is_authenticated
  * is_active
  * is_user
  * is_admin

To be removed again in OctoPrint 1.5.0
This commit is contained in:
Gina Häußge 2017-02-21 12:00:51 +01:00
parent 0adb62b485
commit b7a0f5bee5
2 changed files with 91 additions and 7 deletions

View file

@ -336,6 +336,7 @@ class Server(object):
loginManager = LoginManager()
loginManager.session_protection = "strong"
loginManager.user_callback = load_user
loginManager.anonymous_user = users.AnonymousUser # TODO: remove in 1.5.0
if not userManager.enabled:
loginManager.anonymous_user = users.DummyUser
principals.identity_loaders.appendleft(users.dummy_identity_loader)

View file

@ -5,7 +5,7 @@ __author__ = "Gina Häußge <osd@foosel.net>"
__license__ = 'GNU Affero General Public License http://www.gnu.org/licenses/agpl.html'
__copyright__ = "Copyright (C) 2014 The OctoPrint Project - Released under terms of the AGPLv3 License"
from flask_login import UserMixin
from flask_login import UserMixin, AnonymousUserMixin
from flask_principal import Identity
from werkzeug.local import LocalProxy
import hashlib
@ -413,6 +413,65 @@ class UnknownRole(Exception):
##~~ User object
class MethodReplacedByBooleanProperty(object):
def __init__(self, name, message, getter):
self._name = name
self._message = message
self._getter = getter
@property
def _attr(self):
return self._getter()
def __call__(self):
from warnings import warn
warn(DeprecationWarning(self._message.format(name=self._name)), stacklevel=2)
return self._attr
def __eq__(self, other):
return self._attr == other
def __ne__(self, other):
return self._attr != other
def __bool__(self):
# Python 3
return self._attr
def __nonzero__(self):
# Python 2
return self._attr
def __hash__(self):
return hash(self._attr)
def __repr__(self):
return "MethodReplacedByProperty({}, {}, {})".format(self._name, self._message, self._getter)
def __str__(self):
return str(self._attr)
# TODO: Remove compatibility layer in OctoPrint 1.5.0
class FlaskLoginMethodReplacedByBooleanProperty(MethodReplacedByBooleanProperty):
def __init__(self, name, getter):
message = "{name} is now a property in Flask-Login versions >= 0.3.0, which OctoPrint now uses. " + \
"Use {name} instead of {name}(). This compatibility layer will be removed in OctoPrint 1.5.0."
MethodReplacedByBooleanProperty.__init__(self, name, message, getter)
# TODO: Remove compatibility layer in OctoPrint 1.5.0
class OctoPrintUserMethodReplacedByBooleanProperty(MethodReplacedByBooleanProperty):
def __init__(self, name, getter):
message = "{name} is now a property for consistency reasons with Flask-Login versions >= 0.3.0, which " + \
"OctoPrint now uses. Use {name} instead of {name}(). This compatibility layer will be removed " + \
"in OctoPrint 1.5.0."
MethodReplacedByBooleanProperty.__init__(self, name, message, getter)
class User(UserMixin):
def __init__(self, username, passwordHash, active, roles, apikey=None, settings=None):
self._username = username
@ -428,9 +487,9 @@ class User(UserMixin):
def asDict(self):
return {
"name": self._username,
"active": self.is_active,
"admin": self.is_admin,
"user": self.is_user,
"active": bool(self.is_active),
"admin": bool(self.is_admin),
"user": bool(self.is_user),
"apikey": self._apikey,
"settings": self._settings
}
@ -444,17 +503,25 @@ class User(UserMixin):
def get_name(self):
return self._username
@property
def is_anonymous(self):
return FlaskLoginMethodReplacedByBooleanProperty("is_anonymous", lambda: False)
@property
def is_authenticated(self):
return FlaskLoginMethodReplacedByBooleanProperty("is_authenticated", lambda: True)
@property
def is_active(self):
return self._active
return FlaskLoginMethodReplacedByBooleanProperty("is_active", lambda: self._active)
@property
def is_user(self):
return "user" in self._roles
return OctoPrintUserMethodReplacedByBooleanProperty("is_user", lambda: "user" in self._roles)
@property
def is_admin(self):
return "admin" in self._roles
return OctoPrintUserMethodReplacedByBooleanProperty("is_admin", lambda: "admin" in self._roles)
def get_all_settings(self):
return self._settings
@ -505,6 +572,22 @@ class User(UserMixin):
def __repr__(self):
return "User(id=%s,name=%s,active=%r,user=%r,admin=%r)" % (self.get_id(), self.get_name(), self.is_active, self.is_user, self.is_admin)
class AnonymousUser(AnonymousUserMixin):
@property
def is_anonymous(self):
return FlaskLoginMethodReplacedByBooleanProperty("is_anonymous", lambda: True)
@property
def is_authenticated(self):
return FlaskLoginMethodReplacedByBooleanProperty("is_authenticated", lambda: False)
@property
def is_active(self):
return FlaskLoginMethodReplacedByBooleanProperty("is_active", lambda: False)
class SessionUser(User):
def __init__(self, user):
self._user = user