fix(users): Fix deleting users

Remove all internal references, e.g. sessions, attributes, password reset requests.

Add hook for plugins.

If not deletable remove at least all personal data
This commit is contained in:
Ferdinand Thiessen 2021-12-02 21:27:59 +01:00
parent 50fa39be4f
commit d0674e8876
5 changed files with 45 additions and 13 deletions

View File

@ -1,5 +1,6 @@
import secrets import secrets
from io import BytesIO from io import BytesIO
from sqlalchemy import exc
from flask import current_app from flask import current_app
from datetime import datetime, timedelta, timezone from datetime import datetime, timedelta, timezone
from flask.helpers import send_file from flask.helpers import send_file
@ -168,11 +169,34 @@ def find_user(uid_mail):
return user return user
def delete(user): @Hook
def delete_user(user: User):
"""Delete given user""" """Delete given user"""
# First let the backend delete the user, as this might fail
current_app.config["FG_AUTH_BACKEND"].delete_user(user) current_app.config["FG_AUTH_BACKEND"].delete_user(user)
# Clear all easy relationships
user.avatar_ = None
user._attributes.clear()
user.roles_.clear()
user.sessions_.clear()
user.reset_requests_.clear()
db.session.commit()
try:
# Delete the user
db.session.delete(user) db.session.delete(user)
db.session.commit() db.session.commit()
except exc.IntegrityError:
logger.error("Delete of user failed, there might be ForeignKey contraits from disabled plugins", exec_info=True)
# Remove at least all personal data
user.userid = f"__deleted_user__{user.id_}"
user.display_name = "DELETED USER"
user.firstname = ""
user.lastname = ""
user.birthday = None
user.mail = None
db.session.commit()
def register(data): def register(data):

View File

@ -5,10 +5,9 @@ from typing import Optional
from datetime import date, datetime from datetime import date, datetime
from sqlalchemy.orm.collections import attribute_mapped_collection from sqlalchemy.orm.collections import attribute_mapped_collection
from flaschengeist.models.image import Image
from ..database import db from ..database import db
from . import ModelSerializeMixin, UtcDateTime, Serial from . import ModelSerializeMixin, UtcDateTime, Serial
from .image import Image
association_table = db.Table( association_table = db.Table(

View File

@ -27,6 +27,11 @@ before_update_user = HookBefore("update_user")
Args: Args:
user: User object user: User object
""" """
before_delete_user = HookBefore("delete_user")
"""Hook decorator,this is called before an user gets deleted.
Args:
user: User object
"""
class Plugin: class Plugin:

View File

@ -6,8 +6,7 @@ Flaschengeist database (as User attribute)
import os import os
import hashlib import hashlib
import binascii import binascii
from werkzeug.exceptions import BadRequest, NotFound from werkzeug.exceptions import BadRequest
from flaschengeist.plugins import AuthPlugin from flaschengeist.plugins import AuthPlugin
from flaschengeist.models.user import User, Role, Permission from flaschengeist.models.user import User, Role, Permission
from flaschengeist.database import db from flaschengeist.database import db
@ -18,6 +17,8 @@ class AuthPlain(AuthPlugin):
def post_install(self): def post_install(self):
if User.query.first() is None: if User.query.first() is None:
logger.info("Installing admin user") logger.info("Installing admin user")
role = Role.query.filter(Role.name == "Superuser").first()
if role is None:
role = Role(name="Superuser", permissions=Permission.query.all()) role = Role(name="Superuser", permissions=Permission.query.all())
admin = User( admin = User(
userid="admin", userid="admin",

View File

@ -2,18 +2,17 @@
Provides routes used to manage users Provides routes used to manage users
""" """
from io import BytesIO
from http.client import NO_CONTENT, CREATED from http.client import NO_CONTENT, CREATED
from flask import Blueprint, request, jsonify, make_response, Response, send_file from flask import Blueprint, request, jsonify, make_response
from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed, NotFound from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed, NotFound
from . import permissions from . import permissions
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.config import config from flaschengeist.config import config
from flaschengeist.plugins import Plugin from flaschengeist.plugins import Plugin
from flaschengeist.models.user import User, _Avatar from flaschengeist.models.user import User
from flaschengeist.utils.decorators import login_required, extract_session, headers from flaschengeist.utils.decorators import login_required, extract_session, headers
from flaschengeist.controller import userController, imageController as image_controller from flaschengeist.controller import userController
from flaschengeist.utils.HTTP import created, no_content from flaschengeist.utils.HTTP import created, no_content
from flaschengeist.utils.datetime import from_iso_format from flaschengeist.utils.datetime import from_iso_format
@ -23,6 +22,10 @@ class UsersPlugin(Plugin):
blueprint = Blueprint(name, __name__) blueprint = Blueprint(name, __name__)
permissions = permissions.permissions permissions = permissions.permissions
def install(self):
userController.install()
return super().install()
@UsersPlugin.blueprint.route("/users", methods=["POST"]) @UsersPlugin.blueprint.route("/users", methods=["POST"])
def register(): def register():
@ -144,7 +147,7 @@ def delete_avatar(userid, current_session):
if userid != current_session.user_.userid and not current_session.user_.has_permission(permissions.EDIT): if userid != current_session.user_.userid and not current_session.user_.has_permission(permissions.EDIT):
raise Forbidden raise Forbidden
userController.delete_avatar(user) userController.delete_avatar(user)
return "", NO_CONTENT return no_content()
@UsersPlugin.blueprint.route("/users/<userid>", methods=["DELETE"]) @UsersPlugin.blueprint.route("/users/<userid>", methods=["DELETE"])
@ -163,8 +166,8 @@ def delete_user(userid, current_session):
""" """
logger.debug("Delete user {{ {} }}".format(userid)) logger.debug("Delete user {{ {} }}".format(userid))
user = userController.get_user(userid) user = userController.get_user(userid)
userController.delete(user) userController.delete_user(user)
return "", NO_CONTENT return no_content()
@UsersPlugin.blueprint.route("/users/<userid>", methods=["PUT"]) @UsersPlugin.blueprint.route("/users/<userid>", methods=["PUT"])
@ -217,7 +220,7 @@ def edit_user(userid, current_session):
userController.modify_user(user, password, new_password) userController.modify_user(user, password, new_password)
userController.update_user(user) userController.update_user(user)
return "", NO_CONTENT return no_content()
@UsersPlugin.blueprint.route("/notifications", methods=["GET"]) @UsersPlugin.blueprint.route("/notifications", methods=["GET"])