feature/pricelist #15
|
@ -54,6 +54,8 @@ def __load_plugins(app):
|
|||
logger.error(
|
||||
f"Plugin {entry_point.name} was enabled, but could not be loaded due to an error.", exc_info=True
|
||||
)
|
||||
del plugin
|
||||
continue
|
||||
if isinstance(plugin, AuthPlugin):
|
||||
logger.debug(f"Found authentication plugin: {entry_point.name}")
|
||||
if entry_point.name == config["FLASCHENGEIST"]["auth"]:
|
||||
|
@ -65,6 +67,7 @@ def __load_plugins(app):
|
|||
app.config["FG_PLUGINS"][entry_point.name] = plugin
|
||||
if "FG_AUTH_BACKEND" not in app.config:
|
||||
logger.error("No authentication plugin configured or authentication plugin not found")
|
||||
raise RuntimeError("No authentication plugin configured or authentication plugin not found")
|
||||
|
||||
|
||||
def install_all():
|
||||
|
|
|
@ -2,7 +2,7 @@ from sqlalchemy.exc import IntegrityError
|
|||
from werkzeug.exceptions import BadRequest, NotFound
|
||||
|
||||
from flaschengeist.models.user import Role, Permission
|
||||
from flaschengeist.database import db
|
||||
from flaschengeist.database import db, case_sensitive
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.utils.hook import Hook
|
||||
|
||||
|
@ -36,8 +36,8 @@ def update_role(role, new_name):
|
|||
except IntegrityError:
|
||||
logger.debug("IntegrityError: Role might still be in use", exc_info=True)
|
||||
raise BadRequest("Role still in use")
|
||||
elif role.name != new_name:
|
||||
if db.session.query(db.exists().where(Role.name == new_name)).scalar():
|
||||
else:
|
||||
if role.name == new_name or db.session.query(db.exists().where(Role.name == case_sensitive(new_name))).scalar():
|
||||
raise BadRequest("Name already used")
|
||||
role.name = new_name
|
||||
db.session.commit()
|
||||
|
|
|
@ -1,3 +1,10 @@
|
|||
from flask_sqlalchemy import SQLAlchemy
|
||||
|
||||
db = SQLAlchemy()
|
||||
|
||||
|
||||
def case_sensitive(s):
|
||||
if db.session.bind.dialect.name == "mysql":
|
||||
from sqlalchemy import func
|
||||
return func.binary(s)
|
||||
return s
|
||||
|
|
|
@ -29,17 +29,16 @@ level = "WARNING"
|
|||
[auth_plain]
|
||||
enabled = true
|
||||
|
||||
#[auth_ldap]
|
||||
# enabled = true
|
||||
# host =
|
||||
# port =
|
||||
# bind_dn =
|
||||
# base_dn =
|
||||
# secret =
|
||||
# use_ssl =
|
||||
# admin_dn =
|
||||
# admin_dn =
|
||||
# default_gid =
|
||||
[auth_ldap]
|
||||
# Full documentation https://flaschengeist.dev/Flaschengeist/flaschengeist/wiki/plugins_auth_ldap
|
||||
enabled = false
|
||||
# host = "localhost"
|
||||
# port = 389
|
||||
# base_dn = "dc=example,dc=com"
|
||||
# root_dn = "cn=Manager,dc=example,dc=com"
|
||||
# root_secret = "SuperS3cret"
|
||||
# Uncomment to use secured LDAP (ldaps)
|
||||
# use_ssl = true
|
||||
|
||||
[MESSAGES]
|
||||
welcome_subject = "Welcome to Flaschengeist {name}"
|
||||
|
|
|
@ -56,7 +56,7 @@ class User(db.Model, ModelSerializeMixin):
|
|||
display_name: str = db.Column(db.String(30))
|
||||
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)
|
||||
mail: str = db.Column(db.String(60))
|
||||
birthday: Optional[date] = db.Column(db.Date)
|
||||
roles: list[str] = []
|
||||
permissions: Optional[list[str]] = None
|
||||
|
|
|
@ -33,6 +33,7 @@ class Plugin:
|
|||
|
||||
blueprint = None # You have to override
|
||||
permissions = [] # You have to override
|
||||
id = "dev.flaschengeist.plugin" # You have to override
|
||||
name = "plugin" # You have to override
|
||||
models = None # You have to override
|
||||
|
||||
|
@ -94,7 +95,7 @@ class Plugin:
|
|||
db.session.commit()
|
||||
|
||||
def notify(self, user, text: str, data=None):
|
||||
n = Notification(text=text, data=data, plugin=self.name, user_=user)
|
||||
n = Notification(text=text, data=data, plugin=self.id, user_=user)
|
||||
db.session.add(n)
|
||||
db.session.commit()
|
||||
|
||||
|
|
|
@ -1,56 +1,61 @@
|
|||
"""LDAP Authentication Provider Plugin"""
|
||||
import io
|
||||
import os
|
||||
import ssl
|
||||
from typing import Optional
|
||||
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 ldap3 import SUBTREE, MODIFY_REPLACE, MODIFY_ADD, MODIFY_DELETE
|
||||
from werkzeug.exceptions import BadRequest, InternalServerError, NotFound
|
||||
|
||||
from flaschengeist import logger
|
||||
from flaschengeist.plugins import AuthPlugin, after_role_updated
|
||||
from flaschengeist.plugins import AuthPlugin, before_role_updated
|
||||
from flaschengeist.models.user import User, Role, _Avatar
|
||||
import flaschengeist.controller.userController as userController
|
||||
|
||||
|
||||
class AuthLDAP(AuthPlugin):
|
||||
def __init__(self, cfg):
|
||||
def __init__(self, config):
|
||||
super().__init__()
|
||||
config = {"port": 389, "use_ssl": False}
|
||||
config.update(cfg)
|
||||
app.config.update(
|
||||
LDAP_SERVER=config["host"],
|
||||
LDAP_PORT=config["port"],
|
||||
LDAP_BINDDN=config["bind_dn"],
|
||||
LDAP_SERVER=config.get("host", "localhost"),
|
||||
LDAP_PORT=config.get("port", 389),
|
||||
LDAP_BINDDN=config.get("bind_dn", None),
|
||||
LDAP_SECRET=config.get("secret", None),
|
||||
LDAP_USE_SSL=config.get("use_ssl", False),
|
||||
# That's not TLS, its dirty StartTLS on unencrypted LDAP
|
||||
LDAP_USE_TLS=False,
|
||||
LDAP_USE_SSL=config["use_ssl"],
|
||||
LDAP_TLS_VERSION=ssl.PROTOCOL_TLSv1_2,
|
||||
LDAP_REQUIRE_CERT=ssl.CERT_NONE,
|
||||
LDAP_TLS_VERSION=ssl.PROTOCOL_TLS,
|
||||
FORCE_ATTRIBUTE_VALUE_AS_LIST=True,
|
||||
)
|
||||
if "secret" in config:
|
||||
app.config["LDAP_SECRET"] = config["secret"]
|
||||
logger.warning(app.config.get("LDAP_USE_SSL"))
|
||||
if "ca_cert" in config:
|
||||
app.config["LDAP_CA_CERTS_FILE"] = config["ca_cert"]
|
||||
else:
|
||||
# Default is CERT_REQUIRED
|
||||
app.config["LDAP_REQUIRE_CERT"] = ssl.CERT_OPTIONAL
|
||||
self.ldap = LDAPConn(app)
|
||||
self.dn = config["base_dn"]
|
||||
self.default_gid = config["default_gid"]
|
||||
self.base_dn = config["base_dn"]
|
||||
self.search_dn = config.get("search_dn", "ou=people,{base_dn}").format(base_dn=self.base_dn)
|
||||
self.group_dn = config.get("group_dn", "ou=group,{base_dn}").format(base_dn=self.base_dn)
|
||||
self.password_hash = config.get("password_hash", "SSHA").upper()
|
||||
self.object_classes = config.get("object_classes", ["inetOrgPerson"])
|
||||
self.user_attributes: dict = config.get("user_attributes", {})
|
||||
|
||||
# TODO: might not be set if modify is called
|
||||
if "admin_dn" in config:
|
||||
self.admin_dn = config["admin_dn"]
|
||||
self.admin_secret = config["admin_secret"]
|
||||
else:
|
||||
self.admin_dn = None
|
||||
self.root_dn = config.get("root_dn", None)
|
||||
self.root_secret = config.get("root_secret", None)
|
||||
|
||||
@after_role_updated
|
||||
@before_role_updated
|
||||
def _role_updated(role, new_name):
|
||||
logger.debug(f"LDAP: before_role_updated called with ({role}, {new_name})")
|
||||
self.__modify_role(role, new_name)
|
||||
|
||||
def login(self, user, password):
|
||||
if not user:
|
||||
return False
|
||||
return self.ldap.authenticate(user.userid, password, "uid", self.dn)
|
||||
return self.ldap.authenticate(user.userid, password, "uid", self.base_dn)
|
||||
|
||||
def find_user(self, userid, mail=None):
|
||||
attr = self.__find(userid, mail)
|
||||
|
@ -64,42 +69,41 @@ class AuthLDAP(AuthPlugin):
|
|||
self.__update(user, attr)
|
||||
|
||||
def create_user(self, user, password):
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
if self.root_dn is None:
|
||||
logger.error("root_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
|
||||
try:
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
ldap_conn = self.ldap.connect(self.root_dn, self.root_secret)
|
||||
attributes = self.user_attributes.copy()
|
||||
if "uidNumber" in attributes:
|
||||
self.ldap.connection.search(
|
||||
"ou=user,{}".format(self.dn),
|
||||
self.search_dn,
|
||||
"(uidNumber=*)",
|
||||
SUBTREE,
|
||||
attributes=["uidNumber"],
|
||||
)
|
||||
uid_number = (
|
||||
sorted(self.ldap.response(), key=lambda i: i["attributes"]["uidNumber"], reverse=True,)[0][
|
||||
"attributes"
|
||||
]["uidNumber"]
|
||||
+ 1
|
||||
resp = sorted(
|
||||
self.ldap.response(),
|
||||
key=lambda i: i["attributes"]["uidNumber"],
|
||||
reverse=True,
|
||||
)
|
||||
dn = f"cn={user.firstname} {user.lastname},ou=user,{self.dn}"
|
||||
object_class = [
|
||||
"inetOrgPerson",
|
||||
"posixAccount",
|
||||
"person",
|
||||
"organizationalPerson",
|
||||
]
|
||||
attributes = {
|
||||
"sn": user.firstname,
|
||||
"givenName": user.lastname,
|
||||
"gidNumber": self.default_gid,
|
||||
"homeDirectory": f"/home/{user.userid}",
|
||||
"loginShell": "/bin/bash",
|
||||
attributes = resp[0]["attributes"]["uidNumber"] + 1 if resp else attributes["uidNumber"]
|
||||
dn = self.dn_template.format(
|
||||
firstname=user.firstname,
|
||||
lastname=user.lastname,
|
||||
userid=user.userid,
|
||||
mail=user.mail,
|
||||
display_name=user.display_name,
|
||||
base_dn=self.base_dn,
|
||||
)
|
||||
attributes.update({
|
||||
"sn": user.lastname,
|
||||
"givenName": user.firstname,
|
||||
"uid": user.userid,
|
||||
"userPassword": hashed(HASHED_SALTED_MD5, password),
|
||||
"uidNumber": uid_number,
|
||||
}
|
||||
ldap_conn.add(dn, object_class, attributes)
|
||||
"userPassword": self.__hash(password),
|
||||
})
|
||||
ldap_conn.add(dn, self.object_classes, attributes)
|
||||
self._set_roles(user)
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
raise BadRequest
|
||||
|
@ -110,10 +114,10 @@ class AuthLDAP(AuthPlugin):
|
|||
if password:
|
||||
ldap_conn = self.ldap.connect(dn, password)
|
||||
else:
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
if self.root_dn is None:
|
||||
logger.error("root_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
ldap_conn = self.ldap.connect(self.root_dn, self.root_secret)
|
||||
modifier = {}
|
||||
for name, ldap_name in [
|
||||
("firstname", "givenName"),
|
||||
|
@ -124,9 +128,7 @@ class AuthLDAP(AuthPlugin):
|
|||
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])]
|
||||
modifier["userPassword"] = [(MODIFY_REPLACE, [self.__hash(new_password)])]
|
||||
ldap_conn.modify(dn, modifier)
|
||||
self._set_roles(user)
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
|
@ -134,7 +136,7 @@ class AuthLDAP(AuthPlugin):
|
|||
|
||||
def get_avatar(self, user):
|
||||
self.ldap.connection.search(
|
||||
"ou=user,{}".format(self.dn),
|
||||
self.search_dn,
|
||||
"(uid={})".format(user.userid),
|
||||
SUBTREE,
|
||||
attributes=["jpegPhoto"],
|
||||
|
@ -150,8 +152,8 @@ class AuthLDAP(AuthPlugin):
|
|||
raise NotFound
|
||||
|
||||
def set_avatar(self, user, avatar: _Avatar):
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
if self.root_dn is None:
|
||||
logger.error("root_dn missing in ldap config!")
|
||||
raise InternalServerError
|
||||
|
||||
if avatar.mimetype != "image/jpeg":
|
||||
|
@ -172,16 +174,16 @@ class AuthLDAP(AuthPlugin):
|
|||
raise BadRequest("Unsupported image format")
|
||||
|
||||
dn = user.get_attribute("DN")
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
ldap_conn = self.ldap.connect(self.root_dn, self.root_secret)
|
||||
ldap_conn.modify(dn, {"jpegPhoto": [(MODIFY_REPLACE, [avatar.binary])]})
|
||||
|
||||
def __find(self, userid, mail=None):
|
||||
"""Find attributes of an user by uid or mail in LDAP"""
|
||||
con = self.ldap.connection
|
||||
if not con:
|
||||
con = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
con = self.ldap.connect(self.root_dn, self.root_secret)
|
||||
con.search(
|
||||
f"ou=user,{self.dn}",
|
||||
self.search_dn,
|
||||
f"(| (uid={userid})(mail={mail}))" if mail else f"(uid={userid})",
|
||||
SUBTREE,
|
||||
attributes=["uid", "givenName", "sn", "mail"],
|
||||
|
@ -205,12 +207,12 @@ class AuthLDAP(AuthPlugin):
|
|||
role: Role,
|
||||
new_name: Optional[str],
|
||||
):
|
||||
if self.admin_dn is None:
|
||||
logger.error("admin_dn missing in ldap config!")
|
||||
if self.root_dn is None:
|
||||
logger.error("root_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={role.name})", SUBTREE, attributes=["cn"])
|
||||
ldap_conn = self.ldap.connect(self.root_dn, self.root_secret)
|
||||
ldap_conn.search(self.group_dn, f"(cn={role.name})", SUBTREE, attributes=["cn"])
|
||||
if len(ldap_conn.response) > 0:
|
||||
dn = ldap_conn.response[0]["dn"]
|
||||
if new_name:
|
||||
|
@ -218,13 +220,33 @@ class AuthLDAP(AuthPlugin):
|
|||
else:
|
||||
ldap_conn.delete(dn)
|
||||
|
||||
except (LDAPPasswordIsMandatoryError, LDAPBindError):
|
||||
except LDAPPasswordIsMandatoryError:
|
||||
raise BadRequest
|
||||
except LDAPBindError:
|
||||
logger.debug(f"Could not bind to LDAP server", exc_info=True)
|
||||
raise InternalServerError
|
||||
|
||||
def __hash(self, password):
|
||||
if self.password_hash == "ARGON2":
|
||||
from argon2 import PasswordHasher
|
||||
|
||||
return f"{{ARGON2}}{PasswordHasher().hash(password)}"
|
||||
else:
|
||||
from hashlib import pbkdf2_hmac, sha1
|
||||
import base64
|
||||
|
||||
salt = os.urandom(16)
|
||||
if self.password_hash == "PBKDF2":
|
||||
rounds = 200000
|
||||
password_hash = base64.b64encode(pbkdf2_hmac("sha512", password.encode("utf-8"), salt, rounds)).decode()
|
||||
return f"{{PBKDF2-SHA512}}{rounds}${base64.b64encode(salt).decode()}${password_hash}"
|
||||
else:
|
||||
return f"{{SSHA}}{base64.b64encode(sha1(password + salt) + salt)}"
|
||||
|
||||
def _get_groups(self, uid):
|
||||
groups = []
|
||||
self.ldap.connection.search(
|
||||
"ou=group,{}".format(self.dn),
|
||||
self.group_dn,
|
||||
"(memberUID={})".format(uid),
|
||||
SUBTREE,
|
||||
attributes=["cn"],
|
||||
|
@ -236,7 +258,7 @@ class AuthLDAP(AuthPlugin):
|
|||
|
||||
def _get_all_roles(self):
|
||||
self.ldap.connection.search(
|
||||
f"ou=group,{self.dn}",
|
||||
self.group_dn,
|
||||
"(cn=*)",
|
||||
SUBTREE,
|
||||
attributes=["cn", "gidNumber", "memberUid"],
|
||||
|
@ -245,8 +267,7 @@ class AuthLDAP(AuthPlugin):
|
|||
|
||||
def _set_roles(self, user: User):
|
||||
try:
|
||||
ldap_conn = self.ldap.connect(self.admin_dn, self.admin_secret)
|
||||
|
||||
ldap_conn = self.ldap.connect(self.root_dn, self.root_secret)
|
||||
ldap_roles = self._get_all_roles()
|
||||
|
||||
gid_numbers = sorted(ldap_roles, key=lambda i: i["attributes"]["gidNumber"], reverse=True)
|
||||
|
@ -255,7 +276,7 @@ class AuthLDAP(AuthPlugin):
|
|||
for user_role in user.roles:
|
||||
if user_role not in [role["attributes"]["cn"][0] for role in ldap_roles]:
|
||||
ldap_conn.add(
|
||||
f"cn={user_role},ou=group,{self.dn}",
|
||||
f"cn={user_role},{self.group_dn}",
|
||||
["posixGroup"],
|
||||
attributes={"gidNumber": gid_number},
|
||||
)
|
||||
|
|
|
@ -113,7 +113,9 @@ def get_transaction(transaction_id) -> Transaction:
|
|||
return transaction
|
||||
|
||||
|
||||
def get_transactions(user, start=None, end=None, limit=None, offset=None, show_reversal=False, show_cancelled=True):
|
||||
def get_transactions(
|
||||
user, start=None, end=None, limit=None, offset=None, show_reversal=False, show_cancelled=True, descending=False
|
||||
):
|
||||
count = None
|
||||
query = Transaction.query.filter((Transaction.sender_ == user) | (Transaction.receiver_ == user))
|
||||
if start:
|
||||
|
@ -125,6 +127,9 @@ def get_transactions(user, start=None, end=None, limit=None, offset=None, show_r
|
|||
query = query.filter(Transaction.original_ == None)
|
||||
if not show_cancelled:
|
||||
query = query.filter(Transaction.reversal_id.is_(None))
|
||||
if descending:
|
||||
query = query.order_by(Transaction.time.desc())
|
||||
else:
|
||||
query = query.order_by(Transaction.time)
|
||||
if limit is not None:
|
||||
count = query.count()
|
||||
|
|
|
@ -99,6 +99,32 @@ def set_limit(userid, current_session: Session):
|
|||
return HTTP.no_content()
|
||||
|
||||
|
||||
@BalancePlugin.blueprint.route("/users/balance/limit", methods=["GET", "PUT"])
|
||||
@login_required(permission=permissions.SET_LIMIT)
|
||||
def limits(current_session: Session):
|
||||
"""Get, Modify limit of all users
|
||||
|
||||
Args:
|
||||
current_ession: Session sent with Authorization Header
|
||||
|
||||
Returns:
|
||||
JSON encoded array of userid with limit or HTTP-error
|
||||
"""
|
||||
|
||||
users = userController.get_users()
|
||||
if request.method == "GET":
|
||||
return jsonify([{"userid": user.userid, "limit": user.get_attribute("balance_limit")} for user in users])
|
||||
|
||||
data = request.get_json()
|
||||
try:
|
||||
limit = data["limit"]
|
||||
except (TypeError, KeyError):
|
||||
raise BadRequest
|
||||
for user in users:
|
||||
balance_controller.set_limit(user, limit)
|
||||
return HTTP.no_content()
|
||||
|
||||
|
||||
@BalancePlugin.blueprint.route("/users/<userid>/balance", methods=["GET"])
|
||||
@login_required(permission=permissions.SHOW)
|
||||
def get_balance(userid, current_session: Session):
|
||||
|
@ -170,6 +196,7 @@ def get_transactions(userid, current_session: Session):
|
|||
show_cancelled = request.args.get("showCancelled", True)
|
||||
limit = request.args.get("limit")
|
||||
offset = request.args.get("offset")
|
||||
descending = request.args.get("descending", False)
|
||||
try:
|
||||
if limit is not None:
|
||||
limit = int(limit)
|
||||
|
@ -179,11 +206,20 @@ def get_transactions(userid, current_session: Session):
|
|||
show_reversals = str2bool(show_reversals)
|
||||
if not isinstance(show_cancelled, bool):
|
||||
show_cancelled = str2bool(show_cancelled)
|
||||
if not isinstance(descending, bool):
|
||||
descending = str2bool(descending)
|
||||
except ValueError:
|
||||
raise BadRequest
|
||||
|
||||
transactions, count = balance_controller.get_transactions(
|
||||
user, start, end, limit, offset, show_reversal=show_reversals, show_cancelled=show_cancelled
|
||||
user,
|
||||
start,
|
||||
end,
|
||||
limit,
|
||||
offset,
|
||||
show_reversal=show_reversals,
|
||||
show_cancelled=show_cancelled,
|
||||
descending=descending,
|
||||
)
|
||||
return {"transactions": transactions, "count": count}
|
||||
|
||||
|
|
|
@ -11,6 +11,7 @@ from . import permissions, models
|
|||
|
||||
class EventPlugin(Plugin):
|
||||
name = "events"
|
||||
id = "dev.flaschengeist.events"
|
||||
plugin = LocalProxy(lambda: current_app.config["FG_PLUGINS"][EventPlugin.name])
|
||||
permissions = permissions.permissions
|
||||
blueprint = Blueprint(name, __name__)
|
||||
|
|
|
@ -116,7 +116,7 @@ def get_event(event_id, with_backup=False) -> Event:
|
|||
if event is None:
|
||||
raise NotFound
|
||||
if not with_backup:
|
||||
return clear_backup(event)
|
||||
clear_backup(event)
|
||||
return event
|
||||
|
||||
|
||||
|
@ -153,7 +153,9 @@ def delete_event(event_id):
|
|||
Raises:
|
||||
NotFound if not found
|
||||
"""
|
||||
event = get_event(event_id)
|
||||
event = get_event(event_id, True)
|
||||
for job in event.jobs:
|
||||
delete_job(job)
|
||||
db.session.delete(event)
|
||||
db.session.commit()
|
||||
|
||||
|
@ -201,17 +203,15 @@ def update():
|
|||
|
||||
|
||||
def delete_job(job: Job):
|
||||
for service in job.services:
|
||||
unassign_job(service=service, notify=True)
|
||||
db.session.delete(job)
|
||||
db.session.commit()
|
||||
|
||||
|
||||
def assign_to_job(job: Job, user, value):
|
||||
def assign_job(job: Job, user, value):
|
||||
assert value > 0
|
||||
service = Service.query.get((job.id, user.id_))
|
||||
if value < 0:
|
||||
if not service:
|
||||
raise BadRequest
|
||||
db.session.delete(service)
|
||||
else:
|
||||
if service:
|
||||
service.value = value
|
||||
else:
|
||||
|
@ -220,6 +220,25 @@ def assign_to_job(job: Job, user, value):
|
|||
db.session.commit()
|
||||
|
||||
|
||||
def unassign_job(job: Job = None, user=None, service=None, notify=False):
|
||||
if service is None:
|
||||
assert(job is not None and user is not None)
|
||||
service = Service.query.get((job.id, user.id_))
|
||||
else:
|
||||
user = service.user_
|
||||
if not service:
|
||||
raise BadRequest
|
||||
|
||||
event_id = service.job_.event_id_
|
||||
|
||||
db.session.delete(service)
|
||||
db.session.commit()
|
||||
if notify:
|
||||
EventPlugin.plugin.notify(
|
||||
user, "Your assignmet was cancelled", {"event_id": event_id}
|
||||
)
|
||||
|
||||
|
||||
@scheduled
|
||||
def assign_backups():
|
||||
logger.debug("Notifications")
|
||||
|
|
|
@ -415,7 +415,10 @@ def update_job(event_id, job_id, current_session: Session):
|
|||
user != current_session.user_ and not current_session.user_.has_permission(permissions.ASSIGN_OTHER)
|
||||
):
|
||||
raise Forbidden
|
||||
event_controller.assign_to_job(job, user, value)
|
||||
if value > 0:
|
||||
event_controller.assign_job(job, user, value)
|
||||
else:
|
||||
event_controller.unassign_job(job, user, notify=user != current_session.user_)
|
||||
except (KeyError, ValueError):
|
||||
raise BadRequest
|
||||
|
||||
|
|
7
setup.py
7
setup.py
|
@ -41,7 +41,12 @@ setup(
|
|||
"werkzeug",
|
||||
mysql_driver,
|
||||
],
|
||||
extras_require={"ldap": ["flask_ldapconn", "ldap3"], "pricelist": ["pillow"], "test": ["pytest", "coverage"]},
|
||||
extras_require={
|
||||
"ldap": ["flask_ldapconn", "ldap3"],
|
||||
"argon": ["argon2-cffi"],
|
||||
"pricelist": ["pillow"],
|
||||
"test": ["pytest", "coverage"],
|
||||
},
|
||||
entry_points={
|
||||
"flaschengeist.plugin": [
|
||||
# Authentication providers
|
||||
|
|
Loading…
Reference in New Issue