[Plugin]users, auth_ldap: Implemented avatar
This commit is contained in:
parent
9409533f7c
commit
a270857c41
|
@ -8,6 +8,21 @@ from flaschengeist.database import db
|
|||
from flaschengeist import logger
|
||||
|
||||
|
||||
class Avatar:
|
||||
mimetype = ""
|
||||
binary = bytearray()
|
||||
|
||||
|
||||
@Hook
|
||||
def load_avatar(avatar: Avatar, user: User):
|
||||
pass
|
||||
|
||||
|
||||
@Hook
|
||||
def save_avatar(avatar: Avatar, user: User):
|
||||
pass
|
||||
|
||||
|
||||
def login_user(username, password):
|
||||
logger.info("login user {{ {} }}".format(username))
|
||||
try:
|
||||
|
|
|
@ -54,6 +54,7 @@ class User(db.Model, ModelSerializeMixin):
|
|||
firstname: str = db.Column(db.String(50), nullable=False)
|
||||
lastname: str = db.Column(db.String(50), nullable=False)
|
||||
mail: str = db.Column(db.String(60), nullable=False)
|
||||
avatar_url: Optional[str] = db.Column(db.String(60))
|
||||
birthday: Optional[date] = db.Column(db.Date)
|
||||
roles: [str] = []
|
||||
|
||||
|
|
|
@ -10,6 +10,20 @@ Args:
|
|||
message: Message object to send
|
||||
"""
|
||||
|
||||
load_avatar_hook = HookCall("load_avatar")
|
||||
"""Hook decorator for loading Avatar data
|
||||
Args:
|
||||
avatar: Avatar object
|
||||
user: User object to load from
|
||||
"""
|
||||
|
||||
save_avatar_hook = HookCall("save_avatar")
|
||||
"""Hook decorator for saving Avatar data
|
||||
Args:
|
||||
avatar: Avatar object
|
||||
user: User object to save
|
||||
"""
|
||||
|
||||
update_user_hook = HookCall("update_user")
|
||||
"""Hook decorator, when ever an user update is done, this is called before.
|
||||
Args:
|
||||
|
|
|
@ -2,16 +2,15 @@
|
|||
|
||||
import ssl
|
||||
from typing import Optional
|
||||
|
||||
from ldap3.utils.hashed import hashed
|
||||
from ldap3 import SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE, HASHED_SALTED_MD5
|
||||
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
|
||||
from flask import current_app as app
|
||||
from flask_ldapconn import LDAPConn
|
||||
from flask import current_app as app
|
||||
from ldap3.utils.hashed import hashed
|
||||
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
|
||||
from ldap3 import SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE, HASHED_SALTED_MD5
|
||||
from werkzeug.exceptions import BadRequest, InternalServerError
|
||||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.plugins import AuthPlugin
|
||||
from flaschengeist.plugins import AuthPlugin, load_avatar_hook, save_avatar_hook
|
||||
from flaschengeist.models.user import User
|
||||
import flaschengeist.controller.userController as userController
|
||||
|
||||
|
@ -19,10 +18,8 @@ import flaschengeist.controller.userController as userController
|
|||
class AuthLDAP(AuthPlugin):
|
||||
def __init__(self, cfg):
|
||||
super().__init__()
|
||||
|
||||
config = {"port": 389, "use_ssl": False}
|
||||
config.update(cfg)
|
||||
|
||||
app.config.update(
|
||||
LDAP_SERVER=config["host"],
|
||||
LDAP_PORT=config["port"],
|
||||
|
@ -38,6 +35,7 @@ class AuthLDAP(AuthPlugin):
|
|||
self.ldap = LDAPConn(app)
|
||||
self.dn = config["base_dn"]
|
||||
self.default_gid = config["default_gid"]
|
||||
|
||||
# TODO: might not be set if modify is called
|
||||
if "admin_dn" in config:
|
||||
self.admin_dn = config["admin_dn"]
|
||||
|
@ -45,6 +43,14 @@ class AuthLDAP(AuthPlugin):
|
|||
else:
|
||||
self.admin_dn = None
|
||||
|
||||
@load_avatar_hook
|
||||
def load_avatar(avatar, user):
|
||||
self.__load_avatar(avatar, user)
|
||||
|
||||
@save_avatar_hook
|
||||
def load_avatar(avatar, user):
|
||||
self.__save_avatar(avatar, user)
|
||||
|
||||
def login(self, user, password):
|
||||
if not user:
|
||||
return False
|
||||
|
@ -64,6 +70,7 @@ class AuthLDAP(AuthPlugin):
|
|||
user.lastname = r["sn"][0]
|
||||
if r["mail"]:
|
||||
user.mail = r["mail"][0]
|
||||
user.avatar_url = f"/api/users/{user.userid}/avatar"
|
||||
if "displayName" in r:
|
||||
user.display_name = r["displayName"][0]
|
||||
userController.set_roles(user, self._get_groups(user.userid), create=True)
|
||||
|
@ -109,6 +116,75 @@ class AuthLDAP(AuthPlugin):
|
|||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
||||
def modify_role(self, old_name: str, new_name: Optional[str]):
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
try:
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
ldap_conn.search(f"ou=group,{self.dn}", f"(cn={old_name})", SUBTREE, attributes=["cn"])
|
||||
if len(ldap_conn.response) >= 0:
|
||||
dn = ldap_conn.response[0]["dn"]
|
||||
if new_name:
|
||||
ldap_conn.modify_dn(dn, f"cn={new_name}")
|
||||
else:
|
||||
ldap_conn.delete(dn)
|
||||
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
||||
def modify_user(self, user: User, password=None, new_password=None):
|
||||
try:
|
||||
dn = user.get_attribute("DN")
|
||||
if password:
|
||||
ldap_conn = self.ldap.connect(dn, password)
|
||||
else:
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
modifier = {}
|
||||
for name, ldap_name in [
|
||||
("firstname", "givenName"),
|
||||
("lastname", "sn"),
|
||||
("mail", "mail"),
|
||||
("display_name", "displayName"),
|
||||
]:
|
||||
if hasattr(user, name):
|
||||
modifier[ldap_name] = [(MODIFY_REPLACE, [getattr(user, name)])]
|
||||
if new_password:
|
||||
# TODO: Use secure hash!
|
||||
salted_password = hashed(HASHED_SALTED_MD5, new_password)
|
||||
modifier["userPassword"] = [(MODIFY_REPLACE, [salted_password])]
|
||||
ldap_conn.modify(dn, modifier)
|
||||
self._set_roles(user)
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
||||
def __load_avatar(self, avatar, user):
|
||||
self.ldap.connection.search(
|
||||
"ou=user,{}".format(self.dn),
|
||||
"(uid={})".format(user.userid),
|
||||
SUBTREE,
|
||||
attributes=["uid", "jpegPhoto"],
|
||||
)
|
||||
r = self.ldap.connection.response[0]["attributes"]
|
||||
if r["uid"][0] == user.userid:
|
||||
avatar.mimetype = "image/jpeg"
|
||||
avatar.binary.clear()
|
||||
avatar.binary.extend(r["jpegPhoto"][0])
|
||||
|
||||
def __save_avatar(self, avatar, user):
|
||||
if avatar.mimetype != "image/jpeg":
|
||||
raise BadRequest("Unsupported image Format")
|
||||
# Maybe use Pillow to convert here
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
dn = user.get_attribute("DN")
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
ldap_conn.modify(dn, {"jpegPhoto": [(MODIFY_REPLACE, [avatar.binary])]})
|
||||
|
||||
def _get_groups(self, uid):
|
||||
groups = []
|
||||
self.ldap.connection.search(
|
||||
|
@ -159,49 +235,3 @@ class AuthLDAP(AuthPlugin):
|
|||
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
||||
def modify_role(self, old_name: str, new_name: Optional[str]):
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
try:
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
ldap_conn.search(f"ou=group,{self.dn}", f"(cn={old_name})", SUBTREE, attributes=["cn"])
|
||||
if len(ldap_conn.response) >= 0:
|
||||
dn = ldap_conn.response[0]["dn"]
|
||||
if new_name:
|
||||
ldap_conn.modify_dn(dn, f"cn={new_name}")
|
||||
else:
|
||||
ldap_conn.delete(dn)
|
||||
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
||||
def modify_user(self, user: User, password=None, new_password=None):
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
|
||||
try:
|
||||
dn = user.get_attribute("DN")
|
||||
if password:
|
||||
ldap_conn = self.ldap.connect(dn, password)
|
||||
else:
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
modifier = {}
|
||||
for name, ldap_name in [
|
||||
("firstname", "givenName"),
|
||||
("lastname", "sn"),
|
||||
("mail", "mail"),
|
||||
("display_name", "displayName"),
|
||||
]:
|
||||
if hasattr(user, name):
|
||||
modifier[ldap_name] = [(MODIFY_REPLACE, [getattr(user, name)])]
|
||||
if new_password:
|
||||
# TODO: Use secure hash!
|
||||
salted_password = hashed(HASHED_SALTED_MD5, new_password)
|
||||
modifier["userPassword"] = [(MODIFY_REPLACE, [salted_password])]
|
||||
ldap_conn.modify(dn, modifier)
|
||||
self._set_roles(user)
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
|
|
@ -5,14 +5,15 @@ Provides routes used to manage users
|
|||
from http.client import NO_CONTENT, CREATED
|
||||
|
||||
from flaschengeist.config import config
|
||||
from flask import Blueprint, request, jsonify, make_response
|
||||
from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed
|
||||
from flask import Blueprint, request, jsonify, make_response, Response
|
||||
from werkzeug.exceptions import BadRequest, Forbidden, MethodNotAllowed, NotFound
|
||||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.models.user import User
|
||||
from flaschengeist.plugins import Plugin
|
||||
from flaschengeist.decorator import login_required, extract_session
|
||||
from flaschengeist.controller import userController
|
||||
from flaschengeist.utils.HTTP import created
|
||||
from flaschengeist.utils.datetime import from_iso_format
|
||||
|
||||
users_bp = Blueprint("users", __name__)
|
||||
|
@ -93,6 +94,36 @@ def get_user(userid, current_session):
|
|||
return jsonify(serial)
|
||||
|
||||
|
||||
@users_bp.route("/users/<userid>/avatar", methods=["GET"])
|
||||
def get_avatar(userid):
|
||||
user = userController.get_user(userid)
|
||||
avatar = userController.Avatar()
|
||||
userController.load_avatar(avatar, user)
|
||||
if len(avatar.binary) > 0:
|
||||
response = Response(avatar.binary, mimetype=avatar.mimetype)
|
||||
response.add_etag()
|
||||
return response.make_conditional(request)
|
||||
raise NotFound
|
||||
|
||||
|
||||
@users_bp.route("/users/<userid>/avatar", methods=["POST"])
|
||||
@login_required()
|
||||
def set_avatar(userid, current_session):
|
||||
user = userController.get_user(userid)
|
||||
if userid != current_session._user.userid and not current_session._user.has_permission(_permission_edit):
|
||||
raise Forbidden
|
||||
|
||||
file = request.files.get("file")
|
||||
if file:
|
||||
avatar = userController.Avatar()
|
||||
avatar.mimetype = file.content_type
|
||||
avatar.binary = bytearray(file.stream.read())
|
||||
userController.save_avatar(avatar, user)
|
||||
return created()
|
||||
else:
|
||||
raise BadRequest
|
||||
|
||||
|
||||
@users_bp.route("/users/<userid>", methods=["DELETE"])
|
||||
@login_required(permission=_permission_delete)
|
||||
def delete_user(userid, current_session):
|
||||
|
|
Loading…
Reference in New Issue