forked from Github-Mirrors/canaille
refactor: user permissions lazy loading
This commit is contained in:
parent
30bd71c5b5
commit
1fbb074cc5
9 changed files with 47 additions and 61 deletions
|
@ -78,7 +78,7 @@ def permissions_needed(*args):
|
|||
@wraps(view_function)
|
||||
def decorator(*args, **kwargs):
|
||||
user = current_user()
|
||||
if not user or not permissions.issubset(user._permissions):
|
||||
if not user or not user.can(*permissions):
|
||||
abort(403)
|
||||
return view_function(*args, user=user, **kwargs)
|
||||
|
||||
|
|
|
@ -61,14 +61,6 @@ class User(canaille.core.models.User, LDAPObject):
|
|||
|
||||
return super().match_filter(filter)
|
||||
|
||||
@classmethod
|
||||
def get(cls, *args, **kwargs):
|
||||
user = super().get(*args, **kwargs)
|
||||
if user:
|
||||
user.load_permissions()
|
||||
|
||||
return user
|
||||
|
||||
@property
|
||||
def identifier(self):
|
||||
return self.rdn_value
|
||||
|
@ -123,10 +115,6 @@ class User(canaille.core.models.User, LDAPObject):
|
|||
password.encode("utf-8"),
|
||||
)
|
||||
|
||||
def reload(self):
|
||||
super().reload()
|
||||
self.load_permissions()
|
||||
|
||||
def save(self, *args, **kwargs):
|
||||
group_attr = self.python_attribute_to_ldap("groups")
|
||||
new_groups = self.changes.get(group_attr)
|
||||
|
|
|
@ -244,14 +244,6 @@ class User(canaille.core.models.User, MemoryModel):
|
|||
"groups": ("Group", "members"),
|
||||
}
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.load_permissions()
|
||||
|
||||
def reload(self):
|
||||
super().reload()
|
||||
self.load_permissions()
|
||||
|
||||
@classmethod
|
||||
def get_from_login(cls, login=None, **kwargs):
|
||||
return User.get(user_name=login)
|
||||
|
|
|
@ -14,7 +14,6 @@ from sqlalchemy import or_
|
|||
from sqlalchemy import select
|
||||
from sqlalchemy.orm import Mapped
|
||||
from sqlalchemy.orm import mapped_column
|
||||
from sqlalchemy.orm import reconstructor
|
||||
from sqlalchemy.orm import relationship
|
||||
from sqlalchemy_json import MutableJson
|
||||
from sqlalchemy_utils import PasswordType
|
||||
|
@ -172,18 +171,6 @@ class User(canaille.core.models.User, Base, SqlAlchemyModel):
|
|||
TZDateTime(timezone=True), nullable=True
|
||||
)
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
super().__init__(*args, **kwargs)
|
||||
self.load_permissions()
|
||||
|
||||
def reload(self):
|
||||
super().reload()
|
||||
self.load_permissions()
|
||||
|
||||
@reconstructor
|
||||
def load_permissions(self):
|
||||
super().load_permissions()
|
||||
|
||||
@classmethod
|
||||
def get_from_login(cls, login=None, **kwargs):
|
||||
return User.get(user_name=login)
|
||||
|
|
|
@ -143,7 +143,7 @@ def about():
|
|||
def users(user):
|
||||
table_form = TableForm(
|
||||
models.User,
|
||||
fields=user._readable_fields | user._writable_fields,
|
||||
fields=user.readable_fields | user.writable_fields,
|
||||
formdata=request.form,
|
||||
)
|
||||
if request.form and not table_form.validate():
|
||||
|
@ -404,7 +404,7 @@ def email_confirmation(data, hash):
|
|||
@bp.route("/profile", methods=("GET", "POST"))
|
||||
@permissions_needed("manage_users")
|
||||
def profile_creation(user):
|
||||
form = build_profile_form(user._writable_fields, user._readable_fields)
|
||||
form = build_profile_form(user.writable_fields, user.readable_fields)
|
||||
form.process(CombinedMultiDict((request.files, request.form)) or None)
|
||||
|
||||
for field in form:
|
||||
|
@ -458,8 +458,6 @@ def profile_create(current_app, form):
|
|||
user.set_password(form["password1"].data)
|
||||
user.save()
|
||||
|
||||
user.load_permissions()
|
||||
|
||||
return user
|
||||
|
||||
|
||||
|
@ -488,8 +486,8 @@ def profile_edition_main_form(user, edited_user, emails_readonly):
|
|||
if emails_readonly:
|
||||
available_fields.remove("emails")
|
||||
|
||||
readable_fields = user._readable_fields & available_fields
|
||||
writable_fields = user._writable_fields & available_fields
|
||||
readable_fields = user.readable_fields & available_fields
|
||||
writable_fields = user.writable_fields & available_fields
|
||||
data = {
|
||||
field: getattr(edited_user, field)
|
||||
for field in writable_fields | readable_fields
|
||||
|
@ -509,7 +507,7 @@ def profile_edition_main_form(user, edited_user, emails_readonly):
|
|||
|
||||
def profile_edition_main_form_validation(user, edited_user, profile_form):
|
||||
for field in profile_form:
|
||||
if field.name in edited_user.attributes and field.name in user._writable_fields:
|
||||
if field.name in edited_user.attributes and field.name in user.writable_fields:
|
||||
if isinstance(field, wtforms.FieldList):
|
||||
# too bad wtforms cannot sanitize the list itself
|
||||
data = [value for value in field.data if value] or None
|
||||
|
@ -744,7 +742,7 @@ def profile_settings(user, edited_user):
|
|||
|
||||
def profile_settings_edit(editor, edited_user):
|
||||
menuitem = "profile" if editor.id == editor.id else "users"
|
||||
fields = editor._readable_fields | editor._writable_fields
|
||||
fields = editor.readable_fields | editor.writable_fields
|
||||
|
||||
available_fields = {"password", "groups", "user_name", "lock_date"}
|
||||
data = {
|
||||
|
@ -758,8 +756,8 @@ def profile_settings_edit(editor, edited_user):
|
|||
data["groups"] = [g.id for g in edited_user.groups]
|
||||
|
||||
form = build_profile_form(
|
||||
editor._writable_fields & available_fields,
|
||||
editor._readable_fields & available_fields,
|
||||
editor.writable_fields & available_fields,
|
||||
editor.readable_fields & available_fields,
|
||||
edited_user,
|
||||
)
|
||||
form.process(CombinedMultiDict((request.files, request.form)) or None, data=data)
|
||||
|
@ -774,7 +772,7 @@ def profile_settings_edit(editor, edited_user):
|
|||
|
||||
else:
|
||||
for attribute in form:
|
||||
if attribute.name in available_fields & editor._writable_fields:
|
||||
if attribute.name in available_fields & editor.writable_fields:
|
||||
setattr(edited_user, attribute.name, attribute.data)
|
||||
|
||||
if (
|
||||
|
|
|
@ -239,11 +239,9 @@ class User(Model):
|
|||
lock_date: Optional[datetime.datetime] = None
|
||||
"""A DateTime indicating when the resource was locked."""
|
||||
|
||||
def __init__(self, *args, **kwargs):
|
||||
self._readable_fields = set()
|
||||
self._writable_fields = set()
|
||||
self._permissions = set()
|
||||
super().__init__(*args, **kwargs)
|
||||
_readable_fields = None
|
||||
_writable_fields = None
|
||||
_permissions = None
|
||||
|
||||
@classmethod
|
||||
def get_from_login(cls, login=None, **kwargs) -> Optional["User"]:
|
||||
|
@ -269,12 +267,18 @@ class User(Model):
|
|||
return self.emails[0] if self.emails else None
|
||||
|
||||
def __getattr__(self, name):
|
||||
if name.startswith("can_") and name != "can_read":
|
||||
permission = name[4:]
|
||||
return permission in self._permissions
|
||||
prefix = "can_"
|
||||
if name.startswith(prefix) and name != "can_read":
|
||||
return self.can(name[len(prefix) :])
|
||||
|
||||
return super().__getattr__(name)
|
||||
|
||||
def can(self, *permissions):
|
||||
if self._permissions is None:
|
||||
self.load_permissions()
|
||||
|
||||
return set(permissions).issubset(self._permissions)
|
||||
|
||||
@property
|
||||
def locked(self) -> bool:
|
||||
"""Wether the user account has been locked or has expired."""
|
||||
|
@ -293,6 +297,26 @@ class User(Model):
|
|||
self._readable_fields |= set(details["READ"])
|
||||
self._writable_fields |= set(details["WRITE"])
|
||||
|
||||
def reload(self):
|
||||
self._readable = None
|
||||
self._writable = None
|
||||
self._permissions = None
|
||||
super().reload()
|
||||
|
||||
@property
|
||||
def readable_fields(self):
|
||||
if self._writable_fields is None:
|
||||
self.load_permissions()
|
||||
|
||||
return self._readable_fields
|
||||
|
||||
@property
|
||||
def writable_fields(self):
|
||||
if self._writable_fields is None:
|
||||
self.load_permissions()
|
||||
|
||||
return self._writable_fields
|
||||
|
||||
|
||||
class Group(Model):
|
||||
"""
|
||||
|
|
|
@ -16,14 +16,14 @@
|
|||
render_func=render_field,
|
||||
**kwargs
|
||||
) }}
|
||||
{% elif field.name in edited_user._writable_fields %}
|
||||
{% elif field.name in edited_user.writable_fields %}
|
||||
{{ fui.render_field(
|
||||
field,
|
||||
user=user,
|
||||
render_func=render_field,
|
||||
**kwargs
|
||||
) }}
|
||||
{% elif field.name in edited_user._readable_fields %}
|
||||
{% elif field.name in edited_user.readable_fields %}
|
||||
{{ fui.render_field(
|
||||
field,
|
||||
user=user,
|
||||
|
|
|
@ -24,9 +24,9 @@
|
|||
{% set lock_indicator = field.render_kw and ("readonly" in field.render_kw or "disabled" in field.render_kw) %}
|
||||
{% if edited_user.user_name == user.user_name or lock_indicator or noindicator %}
|
||||
{{ fui.render_field(field, **kwargs) }}
|
||||
{% elif field.name in edited_user._writable_fields %}
|
||||
{% elif field.name in edited_user.writable_fields %}
|
||||
{{ fui.render_field(field, **kwargs) }}
|
||||
{% elif field.name in edited_user._readable_fields %}
|
||||
{% elif field.name in edited_user.readable_fields %}
|
||||
{{ fui.render_field(field, indicator_icon="eye", indicator_text=_("This user cannot edit this field"), **kwargs) }}
|
||||
{% else %}
|
||||
{{ fui.render_field(field, indicator_icon="eye slash", indicator_text=_("This user cannot see this field"), **kwargs) }}
|
||||
|
@ -140,7 +140,7 @@
|
|||
|
||||
<div class="ui right aligned container">
|
||||
<div class="ui stackable buttons">
|
||||
{% if has_account_lockability and "lock_date" in user._writable_fields and not edited_user.locked %}
|
||||
{% if has_account_lockability and "lock_date" in user.writable_fields and not edited_user.locked %}
|
||||
<button type="submit" class="ui right floated basic negative button confirm" name="action" value="confirm-lock" id="lock" formnovalidate>
|
||||
{% trans %}Lock the account{% endtrans %}
|
||||
</button>
|
||||
|
|
|
@ -162,7 +162,6 @@ def user(app, backend):
|
|||
formatted_address="1235, somewhere",
|
||||
)
|
||||
u.save()
|
||||
u.load_permissions()
|
||||
yield u
|
||||
u.delete()
|
||||
|
||||
|
@ -177,7 +176,6 @@ def admin(app, backend):
|
|||
password="admin",
|
||||
)
|
||||
u.save()
|
||||
u.load_permissions()
|
||||
yield u
|
||||
u.delete()
|
||||
|
||||
|
@ -192,7 +190,6 @@ def moderator(app, backend):
|
|||
password="moderator",
|
||||
)
|
||||
u.save()
|
||||
u.load_permissions()
|
||||
yield u
|
||||
u.delete()
|
||||
|
||||
|
|
Loading…
Reference in a new issue