Only use one plugin system, load auth and "normal" plugins at once.

* Added Plugin class, where to inheritate from
This commit is contained in:
Ferdinand Thiessen 2020-10-15 21:58:56 +02:00
parent 60c2637784
commit 2c55edf6a8
14 changed files with 428 additions and 497 deletions

View File

@ -6,6 +6,7 @@ from flask.json import JSONEncoder, jsonify
from werkzeug.exceptions import HTTPException
from . import logger
from .modules import AuthPlugin
from .system.config import config, configure_app
from .system.controller import roleController
@ -30,19 +31,6 @@ class CustomJSONEncoder(JSONEncoder):
return JSONEncoder.default(self, o)
def __load_auth(app):
for entry_point in pkg_resources.iter_entry_points('flaschengeist.auth'):
logger.debug('Found authentication plugin: %s', entry_point.name)
if entry_point.name == config['FLASCHENGEIST']['AUTH']:
app.config['FG_AUTH_BACKEND'] = entry_point.load()()
app.config['FG_AUTH_BACKEND'].configure(
config[entry_point.name] if config.has_section(entry_point.name) else {})
logger.info('Loaded authentication plugin > %s <', entry_point.name)
break
if not app.config['FG_AUTH_BACKEND']:
logger.error('No authentication plugin configured or authentication plugin not found')
def __load_plugins(app):
logger.info('Search for plugins')
app.config['FG_PLUGINS'] = {}
@ -50,10 +38,20 @@ def __load_plugins(app):
logger.debug("Found plugin: >{}<".format(entry_point.name))
plugin = None
if config.get(entry_point.name, 'enabled', fallback=False):
plugin = entry_point.load()()
app.register_blueprint(plugin.blueprint)
logger.info("Loaded plugin >{}<".format(entry_point.name))
app.config["FG_PLUGINS"][entry_point.name] = plugin
plugin = entry_point.load()(config[entry_point.name] if config.has_section(entry_point.name) else {})
if plugin.blueprint:
app.register_blueprint(plugin.blueprint)
logger.info("Load plugin >{}<".format(entry_point.name))
if isinstance(plugin, AuthPlugin):
logger.debug('Found authentication plugin: %s', entry_point.name)
if entry_point.name == config['FLASCHENGEIST']['AUTH']:
app.config['FG_AUTH_BACKEND'] = plugin
else:
del plugin
else:
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')
def install_all():
@ -77,7 +75,6 @@ def create_app():
from .system.database import db
configure_app(app)
db.init_app(app)
__load_auth(app)
__load_plugins(app)
@app.route("/", methods=["GET"])

View File

@ -12,15 +12,21 @@ HOST =
PASSWORD =
DATABASE =
[MAIL]
URL =
PORT =
USER =
PASSWD =
MAIL =
CRYPT = SSL/STARTLS
[auth_plain]
enabled = true
#[mail]
# enabled = true
# SERVER =
# PORT =
# USER =
# PASSWORD =
# MAIL =
# SSL or STARTLS
# CRYPT = SSL
#[auth_ldap]
# enabled = true
# URL =
# PORT =
# BINDDN =

View File

@ -1,5 +1,10 @@
from pyhooks import precall_register
send_message_hook = precall_register("send_message")
class Plugin:
def __init__(self, blueprint, permissions = {}):
def __init__(self, config=None, blueprint=None, permissions={}):
self.blueprint = blueprint
self.permissions = permissions
@ -10,10 +15,7 @@ class Plugin:
pass
class Auth:
def configure(self, config):
pass
class AuthPlugin(Plugin):
def login(self, user, pw):
""" Login routine, MUST BE IMPLEMENTED!

View File

@ -14,12 +14,12 @@ from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import accessTokenController, userController
access_controller = LocalProxy(lambda: accessTokenController.AccessTokenController())
auth_bp = Blueprint('auth', __name__)
def register():
return Plugin(auth_bp)
class AuthRoutePlugin(Plugin):
def __init__(self, conf):
super().__init__(blueprint=auth_bp)
#################################################
# Routes #
@ -31,84 +31,83 @@ def register():
# DELETE: logout / delete token #
#################################################
@auth_bp.route("/auth", methods=['POST'])
def _create_token():
""" Login User
@auth_bp.route("/auth", methods=['POST'])
def _create_token():
""" Login User
Login in User and create an AccessToken for the User.
Requires POST data {'userid': string, 'password': string}
Returns:
A JSON-File with user information and created token or errors
"""
logger.debug("Start log in.")
data = request.get_json()
try:
userid = data['userid']
password = data['password']
except KeyError:
raise BadRequest("Missing parameter(s)")
Login in User and create an AccessToken for the User.
Requires POST data {'userid': string, 'password': string}
Returns:
A JSON-File with user information and created token or errors
"""
logger.debug("Start log in.")
data = request.get_json()
try:
userid = data['userid']
password = data['password']
except KeyError:
raise BadRequest("Missing parameter(s)")
logger.debug("search user {{ {} }} in database".format(userid))
user = userController.login_user(userid, password)
if not user:
raise Unauthorized
logger.debug("user is {{ {} }}".format(user))
token = access_controller.create(user, user_agent=request.user_agent)
logger.debug("access token is {{ {} }}".format(token))
logger.info("User {{ {} }} success login.".format(userid))
logger.debug("search user {{ {} }} in database".format(userid))
user = userController.login_user(userid, password)
if not user:
raise Unauthorized
logger.debug("user is {{ {} }}".format(user))
token = access_controller.create(user, user_agent=request.user_agent)
logger.debug("access token is {{ {} }}".format(token))
logger.info("User {{ {} }} success login.".format(userid))
# Lets cleanup the DB
access_controller.clear_expired()
return jsonify({"user": user, "token": token, "permissions": user.get_permissions()})
# Lets cleanup the DB
access_controller.clear_expired()
return jsonify({"user": user, "token": token, "permissions": user.get_permissions()})
@auth_bp.route("/auth", methods=['GET'])
@login_required()
def _get_tokens(access_token, **kwargs):
tokens = access_controller.get_users_tokens(access_token.user)
return jsonify(tokens)
@auth_bp.route("/auth", methods=['GET'])
@login_required()
def _get_tokens(access_token, **kwargs):
tokens = access_controller.get_users_tokens(access_token.user)
return jsonify(tokens)
@auth_bp.route("/auth/<token>", methods=['DELETE'])
@login_required()
def _delete_token(token, access_token, **kwargs):
logger.debug("Try to delete access token {{ {} }}".format(token))
token = access_controller.get_token(token, access_token.user)
if not token:
logger.debug("Token not found in database!")
# Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden
access_controller.delete_token(token)
access_controller.clear_expired()
return jsonify({"ok": "ok"})
@auth_bp.route("/auth/<token>", methods=['GET'])
@login_required()
def _get_token(token, access_token, **kwargs):
logger.debug("get token {{ {} }}".format(token))
token = access_controller.get_token(token, access_token.user)
if not token:
# Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden
return jsonify(token)
@auth_bp.route("/auth/<token>", methods=['PUT'])
@login_required()
def _set_lifetime(token, access_token, **kwargs):
token = access_controller.get_token(token, access_token.user)
if not token:
# Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden
try:
lifetime = request.get_json()['value']
logger.debug("set lifetime {{ {} }} to access token {{ {} }}".format(lifetime, token))
access_controller.set_lifetime(token, lifetime)
@auth_bp.route("/auth/<token>", methods=['DELETE'])
@login_required()
def _delete_token(token, access_token, **kwargs):
logger.debug("Try to delete access token {{ {} }}".format(token))
token = access_controller.get_token(token, access_token.user)
if not token:
logger.debug("Token not found in database!")
# Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden
access_controller.delete_token(token)
access_controller.clear_expired()
return jsonify({"ok": "ok"})
except (KeyError, TypeError):
raise BadRequest
@auth_bp.route("/auth/<token>", methods=['GET'])
@login_required()
def _get_token(token, access_token, **kwargs):
logger.debug("get token {{ {} }}".format(token))
token = access_controller.get_token(token, access_token.user)
if not token:
# Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden
return jsonify(token)
@auth_bp.route("/auth/<token>", methods=['PUT'])
@login_required()
def _set_lifetime(token, access_token, **kwargs):
token = access_controller.get_token(token, access_token.user)
if not token:
# Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden
try:
lifetime = request.get_json()['value']
logger.debug("set lifetime {{ {} }} to access token {{ {} }}".format(lifetime, token))
access_controller.set_lifetime(token, lifetime)
return jsonify({"ok": "ok"})
except (KeyError, TypeError):
raise BadRequest

View File

@ -1,29 +1,26 @@
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
import ssl
from ldap3.utils.hashed import hashed
from werkzeug.exceptions import BadRequest
import flaschengeist.modules as modules
from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA512
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
from flask import current_app as app
from flask_ldapconn import LDAPConn
from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA512
import ssl
from werkzeug.exceptions import BadRequest
from flaschengeist.modules import AuthPlugin
from flaschengeist.system.models.user import User
from flaschengeist import logger
class AuthLDAP(modules.Auth):
_default = {
'PORT': '389',
'USE_SSL': 'False'
}
ldap = None
dn = None
class AuthLDAP(AuthPlugin):
def __init__(self, config):
super().__init__()
def configure(self, config):
for name in self._default:
defaults = {
'PORT': '389',
'USE_SSL': 'False'
}
for name in defaults:
if name not in config:
config[name] = self._default[name]
config[name] = defaults[name]
app.config.update(
LDAP_SERVER=config['URL'],

View File

@ -0,0 +1,43 @@
import smtplib
from email.mime.multipart import MIMEMultipart
from flaschengeist.system.models.user import User
from flaschengeist.system.controller import userController
from flaschengeist.system.controller.messageController import Message
from . import Plugin, send_message_hook
class MailMessagePlugin(Plugin):
def __init__(self, config):
super().__init__()
self.server = config['SERVER']
self.port = config['PORT']
self.user = config['USER']
self.password = config['PASSWORD']
self.crypt = config['CRYPT']
self.mail = config['MAIL']
@send_message_hook
def send_mail(self, msg: Message):
if isinstance(msg.receiver, User):
recipients = [msg.receiver.mail]
else:
recipients = userController.get_user_by_role(msg.receiver)
mail = MIMEMultipart()
mail['From'] = self.mail
mail['To'] = ", ".join(recipients)
mail['Subject'] = msg.subject
msg.attach(msg.message)
if not self.smtp:
self.__connect()
self.smtp.sendmail(self.mail, recipients, msg.as_string())
def __connect(self):
if self.crypt == 'SSL':
self.smtp = smtplib.SMTP_SSL(self.server, self.port)
if self.crypt == 'STARTTLS':
self.smtp = smtplib.SMTP(self.smtpServer, self.port)
self.smtp.starttls()
self.smtp.login(self.user, self.password)

View File

@ -1,16 +1,16 @@
from flask import Blueprint, request, jsonify
from werkzeug.exceptions import NotFound, BadRequest, Forbidden
from werkzeug.exceptions import NotFound, BadRequest
from flaschengeist.modules import Plugin
from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import roleController
roles_bp = Blueprint("roles", __name__)
permissions = {}
def register():
return Plugin(roles_bp, permissions)
class RolesPlugin(Plugin):
def __init__(self, config):
super().__init__(config, roles_bp)
######################################################
# Routes #
@ -23,66 +23,60 @@ def register():
# DELETE: remove role #
######################################################
@roles_bp.route("/roles", methods=['POST'])
@login_required()
def add_role(self):
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
if "permissions" in data:
permissions = data["permissions"]
role = roleController.create_role(data["name"], permissions)
return jsonify({"ok": "ok", "id": role.id})
@roles_bp.route("/roles", methods=['POST'])
@login_required()
def __add_role():
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
if "permissions" in data:
permissions = data["permissions"]
role = roleController.create_role(data["name"], permissions)
return jsonify({"ok": "ok", "id": role.id})
@roles_bp.route("/roles", methods=['GET'])
@login_required()
def list_roles(self, **kwargs):
roles = roleController.get_roles()
return jsonify(roles)
@roles_bp.route("/roles/permissions", methods=['GET'])
@login_required()
def list_permissions(self, **kwargs):
permissions = roleController.get_permissions()
return jsonify(permissions)
@roles_bp.route("/roles", methods=['GET'])
@login_required()
def __list_roles(**kwargs):
roles = roleController.get_roles()
return jsonify(roles)
@roles_bp.route("/roles/permissions", methods=['GET'])
@login_required()
def __list_permissions(**kwargs):
permissions = roleController.get_permissions()
return jsonify(permissions)
@roles_bp.route("/roles/<rid>", methods=['GET'])
@login_required()
def __get_role(rid, **kwargs):
role = roleController.get_role(rid)
if role:
return jsonify({
"id": role.id,
"name": role,
"permissions": role.permissions
})
raise NotFound
@roles_bp.route("/roles/<rid>", methods=['PUT'])
@login_required()
def __edit_role(rid, **kwargs):
role = roleController.get_role(rid)
if not role:
@roles_bp.route("/roles/<rid>", methods=['GET'])
@login_required()
def __get_role(self, rid, **kwargs):
role = roleController.get_role(rid)
if role:
return jsonify({
"id": role.id,
"name": role,
"permissions": role.permissions
})
raise NotFound
data = request.get_json()
if 'name' in data:
role.name = data["name"]
if "permissions" in data:
roleController.set_permissions(role, data["permissions"])
roleController.update_role(role)
return jsonify({"ok": "ok"})
@roles_bp.route("/roles/<rid>", methods=['PUT'])
@login_required()
def __edit_role(self, rid, **kwargs):
role = roleController.get_role(rid)
if not role:
raise NotFound
data = request.get_json()
if 'name' in data:
role.name = data["name"]
if "permissions" in data:
roleController.set_permissions(role, data["permissions"])
roleController.update_role(role)
return jsonify({"ok": "ok"})
@roles_bp.route("/roles/<rid>", methods=['DELETE'])
@login_required()
def __delete_role(rid, **kwargs):
if not roleController.delete_role(rid):
raise NotFound
@roles_bp.route("/roles/<rid>", methods=['DELETE'])
@login_required()
def __delete_role(self, rid, **kwargs):
if not roleController.delete_role(rid):
raise NotFound
return jsonify({"ok": "ok"})
return jsonify({"ok": "ok"})

View File

@ -1,21 +1,20 @@
from dateutil import parser
from datetime import datetime, timedelta
from flask import Blueprint, request, jsonify
from werkzeug.exceptions import BadRequest, NotFound
from datetime import datetime, timedelta
from flaschengeist.modules import Plugin
from flaschengeist.system.controller import eventController
from flaschengeist.system.database import db
from flaschengeist.system.decorator import login_required
from flaschengeist.system.models.event import EventKind
from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import eventController
schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule")
permissions = {}
def register():
return Plugin(schedule_bp, permissions)
class SchedulePlugin(Plugin):
def __init__(self, config):
super().__init__(blueprint=schedule_bp)
####################################################################################
# Routes #
@ -38,161 +37,159 @@ def register():
# DELETE: remove user #
####################################################################################
@schedule_bp.route("/events/<int:id>", methods=['GET'])
@login_required() # roles=['schedule_read'])
def __get_event(self, id, **kwargs):
event = eventController.get_event(id)
if not event:
raise NotFound
return jsonify(event)
@schedule_bp.route("/events/<int:id>", methods=['GET'])
@login_required() # roles=['schedule_read'])
def __get_event(id, **kwargs):
event = eventController.get_event(id)
if not event:
raise NotFound
return jsonify(event)
@schedule_bp.route("/events", methods=['GET'])
@schedule_bp.route("/events/<int:year>/<int:month>", methods=['GET'])
@schedule_bp.route("/events/<int:year>/<int:month>/<int:day>", methods=['GET'])
@login_required() # roles=['schedule_read'])
def __get_events(self, year=datetime.now().year, month=datetime.now().month, day=None, **kwrags):
"""Get Event objects for specified date (or month or year),
if nothing set then events for current month are returned
@schedule_bp.route("/events", methods=['GET'])
@schedule_bp.route("/events/<int:year>/<int:month>", methods=['GET'])
@schedule_bp.route("/events/<int:year>/<int:month>/<int:day>", methods=['GET'])
@login_required() # roles=['schedule_read'])
def __get_events(year=datetime.now().year, month=datetime.now().month, day=None, **kwrags):
"""Get Event objects for specified date (or month or year),
if nothing set then events for current month are returned
Args:
year (int, optional): year to query, defaults to current year
month (int, optional): month to query (if set), defaults to current month
day (int, optional): day to query events for (if set)
**kwrags: contains at least access_token (see flaschengeist.decorator)
Returns:
JSON list containing events found
Raises:
BadRequest: If date is invalid
"""
try:
begin = datetime(year=year, month=month, day=1)
if day:
begin += timedelta(days=day - 1)
end = begin + timedelta(days=1)
else:
if month == 12:
end = datetime(year=year + 1, month=1, day=1)
Args:
year (int, optional): year to query, defaults to current year
month (int, optional): month to query (if set), defaults to current month
day (int, optional): day to query events for (if set)
**kwrags: contains at least access_token (see flaschengeist.decorator)
Returns:
JSON list containing events found
Raises:
BadRequest: If date is invalid
"""
try:
begin = datetime(year=year, month=month, day=1)
if day:
begin += timedelta(days=day - 1)
end = begin + timedelta(days=1)
else:
end = datetime(year=year, month=month+1, day=1)
if month == 12:
end = datetime(year=year + 1, month=1, day=1)
else:
end = datetime(year=year, month=month+1, day=1)
events = eventController.get_events(begin, end)
return jsonify(events)
except ValueError:
raise BadRequest("Invalid date given")
events = eventController.get_events(begin, end)
return jsonify(events)
except ValueError:
raise BadRequest("Invalid date given")
@schedule_bp.route("/eventKinds", methods=['POST'])
@login_required()
def __new_event_kind(**kwargs):
data = request.get_json()
if "name" not in data:
raise BadRequest
kind = eventController.create_event_kind(data["name"])
return jsonify({"ok": "ok", "id": kind.id})
@schedule_bp.route("/eventKinds", methods=['POST'])
@login_required()
def __new_event_kind(self, **kwargs):
data = request.get_json()
if "name" not in data:
raise BadRequest
kind = eventController.create_event_kind(data["name"])
return jsonify({"ok": "ok", "id": kind.id})
@schedule_bp.route("/slotKinds", methods=["POST"])
@login_required()
def __new_slot_kind(**kwargs):
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
kind = eventController.create_job_kind(data["name"])
return jsonify({"ok": "ok", "id": kind.id})
@schedule_bp.route("/slotKinds", methods=["POST"])
@login_required()
def __new_slot_kind(self, **kwargs):
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
kind = eventController.create_job_kind(data["name"])
return jsonify({"ok": "ok", "id": kind.id})
@schedule_bp.route("/events", methods=['POST'])
@login_required()
def __new_event(**kwargs):
data = request.get_json()
event = eventController.create_event(begin=parser.isoparse(data["begin"]),
end=parser.isoparse(data["end"]),
description=data["description"],
kind=EventKind.query.get(data["kind"]))
return jsonify({"ok": "ok", "id": event.id})
@schedule_bp.route("/events", methods=['POST'])
@login_required()
def __new_event(self, **kwargs):
data = request.get_json()
event = eventController.create_event(begin=parser.isoparse(data["begin"]),
end=parser.isoparse(data["end"]),
description=data["description"],
kind=EventKind.query.get(data["kind"]))
return jsonify({"ok": "ok", "id": event.id})
@schedule_bp.route("/events/<int:id>", methods=["DELETE"])
@login_required()
def __delete_event(id, **kwargs):
if not eventController.delete_event(id):
raise NotFound
db.session.commit()
return jsonify({'ok': 'ok'})
@schedule_bp.route("/events/<int:id>", methods=["DELETE"])
@login_required()
def __delete_event(self, id, **kwargs):
if not eventController.delete_event(id):
raise NotFound
db.session.commit()
return jsonify({'ok': 'ok'})
@schedule_bp.route("/eventKinds/<int:id>", methods=["PUT"])
@login_required()
def __edit_event_kind(id, **kwargs):
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
eventController.rename_event_kind(id, data["name"])
return jsonify({"ok": "ok"})
@schedule_bp.route("/events/<int:event_id>/slots", methods=["GET"])
@login_required()
def __get_slots(event_id, **kwargs):
event = eventController.get_event(event_id)
if not event:
raise NotFound
return jsonify({event.slots})
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["GET"])
@login_required()
def __get_slot(event_id, slot_id, **kwargs):
slot = eventController.get_event_slot(slot_id, event_id)
if slot:
return jsonify(slot)
raise NotFound
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["DELETE"])
@login_required()
def __delete_slot(event_id, slot_id, **kwargs):
if eventController.delete_event_slot(slot_id, event_id):
@schedule_bp.route("/eventKinds/<int:id>", methods=["PUT"])
@login_required()
def __edit_event_kind(self, id, **kwargs):
data = request.get_json()
if not data or "name" not in data:
raise BadRequest
eventController.rename_event_kind(id, data["name"])
return jsonify({"ok": "ok"})
raise NotFound
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["PUT"])
@login_required()
def __update_slot(event_id, slot_id, **kwargs):
data = request.get_json()
if not data:
raise BadRequest
for job in data['jobs']:
eventController.add_job(job.kind, job.user)
if eventController.delete_event_slot(slot_id, event_id):
return jsonify({"ok": "ok"})
raise NotFound
@schedule_bp.route("/events/<int:event_id>/slots", methods=["GET"])
@login_required()
def __get_slots(self, event_id, **kwargs):
event = eventController.get_event(event_id)
if not event:
raise NotFound
return jsonify({event.slots})
@schedule_bp.route("/events/<int:event_id>/slots", methods=["POST"])
@login_required()
def __add_slot(event_id, **kwargs):
event = eventController.get_event(event_id)
if not event:
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["GET"])
@login_required()
def __get_slot(self, event_id, slot_id, **kwargs):
slot = eventController.get_event_slot(slot_id, event_id)
if slot:
return jsonify(slot)
raise NotFound
data = request.get_json()
attr = {"job_slots": []}
try:
if "start" in data:
attr["start"] = parser.isoparse(data["start"])
if "end" in data:
attr["end"] = parser.isoparse(data["end"])
for job in data["jobs"]:
attr["job_slots"].append({"needed_persons": job["needed_persons"], "kind": job["kind"]})
except KeyError:
raise BadRequest("Missing data in request")
eventController.add_slot(event, **attr)
return jsonify({"ok": "ok"})
def __edit_event():
...
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["DELETE"])
@login_required()
def __delete_slot(self, event_id, slot_id, **kwargs):
if eventController.delete_event_slot(slot_id, event_id):
return jsonify({"ok": "ok"})
raise NotFound
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["PUT"])
@login_required()
def __update_slot(self, event_id, slot_id, **kwargs):
data = request.get_json()
if not data:
raise BadRequest
for job in data['jobs']:
eventController.add_job(job.kind, job.user)
if eventController.delete_event_slot(slot_id, event_id):
return jsonify({"ok": "ok"})
raise NotFound
@schedule_bp.route("/events/<int:event_id>/slots", methods=["POST"])
@login_required()
def __add_slot(self, event_id, **kwargs):
event = eventController.get_event(event_id)
if not event:
raise NotFound
data = request.get_json()
attr = {"job_slots": []}
try:
if "start" in data:
attr["start"] = parser.isoparse(data["start"])
if "end" in data:
attr["end"] = parser.isoparse(data["end"])
for job in data["jobs"]:
attr["job_slots"].append({"needed_persons": job["needed_persons"], "kind": job["kind"]})
except KeyError:
raise BadRequest("Missing data in request")
eventController.add_slot(event, **attr)
return jsonify({"ok": "ok"})
def __edit_event(self):
...

View File

@ -10,8 +10,9 @@ users_bp = Blueprint("users", __name__)
permissions = {'EDIT_USER': 'edit_user'}
def register():
return Plugin(users_bp, permissions)
class UsersPlugin(Plugin):
def __init__(self, config):
super().__init__(blueprint=users_bp, permissions=permissions)
#################################################
# Routes #
@ -23,49 +24,45 @@ def register():
# DELETE: remove user #
#################################################
@users_bp.route("/users", methods=['POST'])
def __registration(self):
logger.debug("Register new User...")
return jsonify({"ok": "ok... well not implemented"})
@users_bp.route("/users", methods=['POST'])
def __registration():
logger.debug("Register new User...")
return jsonify({"ok": "ok... well not implemented"})
@users_bp.route("/users", methods=['GET'])
@login_required()
def __list_users(self, **kwargs):
logger.debug("Retrieve list of all users")
users = userController.get_users()
return jsonify(users)
@users_bp.route("/users", methods=['GET'])
@login_required()
def __list_users(**kwargs):
logger.debug("Retrieve list of all users")
users = userController.get_users()
return jsonify(users)
@users_bp.route("/users/<uid>", methods=['GET'])
@login_required()
def __get_user(uid, **kwargs):
logger.debug("Get information of user {{ {} }}".format(uid))
user = userController.get_user(uid)
if user:
return jsonify(user)
raise NotFound
@users_bp.route("/users/<uid>", methods=['PUT'])
@login_required()
def __edit_user(uid, **kwargs):
logger.debug("Modify information of user {{ {} }}".format(uid))
user = userController.get_user(uid)
if not user:
@users_bp.route("/users/<uid>", methods=['GET'])
@login_required()
def __get_user(self, uid, **kwargs):
logger.debug("Get information of user {{ {} }}".format(uid))
user = userController.get_user(uid)
if user:
return jsonify(user)
raise NotFound
if uid != kwargs['access_token'].user.uid and user.has_permissions(permissions['EDIT_USER']):
return Forbidden
@users_bp.route("/users/<uid>", methods=['PUT'])
@login_required()
def __edit_user(self, uid, **kwargs):
logger.debug("Modify information of user {{ {} }}".format(uid))
user = userController.get_user(uid)
if not user:
raise NotFound
data = request.get_json()
if 'password' not in data:
raise BadRequest("Password is missing")
for key in ["firstname", "lastname", "display_name", "mail"]:
if key in data:
setattr(user, key, data[key])
new_password = data['new_password'] if 'new_password' in data else None
userController.modify_user(user, data['password'], new_password)
userController.update_user(user)
return jsonify({"ok": "ok"})
if uid != kwargs['access_token'].user.uid and user.has_permissions(permissions['EDIT_USER']):
return Forbidden
data = request.get_json()
if 'password' not in data:
raise BadRequest("Password is missing")
for key in ["firstname", "lastname", "display_name", "mail"]:
if key in data:
setattr(user, key, data[key])
new_password = data['new_password'] if 'new_password' in data else None
userController.modify_user(user, data['password'], new_password)
userController.update_user(user)
return jsonify({"ok": "ok"})

View File

@ -1,119 +0,0 @@
import smtplib
from datetime import datetime
from email.mime.multipart import MIMEMultipart
from email.mime.text import MIMEText
from email.header import Header
from geruecht.logger import getDebugLogger
from . import mailConfig
debug = getDebugLogger()
class EmailController():
def __init__(self):
debug.info("init email controller")
self.smtpServer = mailConfig['URL']
self.port = mailConfig['port']
self.user = mailConfig['user']
self.passwd = mailConfig['passwd']
self.crypt = mailConfig['crypt']
self.email = mailConfig['email']
debug.debug("smtpServer is {{ {} }}, port is {{ {} }}, user is {{ {} }}, crypt is {{ {} }}, email is {{ {} }}".format(self.smtpServer, self.port, self.user, self.crypt, self.email))
def __connect__(self):
debug.info('connect to email server')
if self.crypt == 'SSL':
self.smtp = smtplib.SMTP_SSL(self.smtpServer, self.port)
log = self.smtp.ehlo()
debug.debug("ehlo is {{ {} }}".format(log))
if self.crypt == 'STARTTLS':
self.smtp = smtplib.SMTP(self.smtpServer, self.port)
log = self.smtp.ehlo()
debug.debug("ehlo is {{ {} }}".format(log))
log = self.smtp.starttls()
debug.debug("starttles is {{ {} }}".format(log))
log = self.smtp.login(self.user, self.passwd)
debug.debug("login is {{ {} }}".format(log))
def jobTransact(self, user, jobtransact):
debug.info("create email jobtransact {{ {} }}for user {{ {} }}".format(jobtransact, user))
date = '{}.{}.{}'.format(jobtransact['on_date']['day'], jobtransact['on_date']['month'], jobtransact['on_date']['year'])
from_user = '{} {}'.format(jobtransact['from_user']['firstname'], jobtransact['from_user']['lastname'])
job_kind = jobtransact['job_kind']
subject = 'Dienstanfrage am {}'.format(date)
text = MIMEText(
"Hallo {} {},\n"
"{} fragt, ob du am {} den Dienst {} übernehmen willst.\nBeantworte die Anfrage im Userportal von Flaschengeist.".format(user.firstname, user.lastname, from_user, date, job_kind['name']), 'plain')
debug.debug("subject is {{ {} }}, text is {{ {} }}".format(subject, text.as_string()))
return (subject, text)
def jobInvite(self, user, jobtransact):
debug.info("create email jobtransact {{ {} }}for user {{ {} }}".format(jobtransact, user))
date = '{}.{}.{}'.format(jobtransact['on_date']['day'], jobtransact['on_date']['month'], jobtransact['on_date']['year'])
from_user = '{} {}'.format(jobtransact['from_user']['firstname'], jobtransact['from_user']['lastname'])
subject = 'Diensteinladung am {}'.format(date)
text = MIMEText(
"Hallo {} {},\n"
"{} fragt, ob du am {} mit Dienst haben willst.\nBeantworte die Anfrage im Userportal von Flaschengeist.".format(user.firstname, user.lastname, from_user, date), 'plain')
debug.debug("subject is {{ {} }}, text is {{ {} }}".format(subject, text.as_string()))
return (subject, text)
def credit(self, user):
debug.info("create email credit for user {{ {} }}".format(user))
subject = Header('Gerücht, bezahle deine Schulden!', 'utf-8')
sum = user.getGeruecht(datetime.now().year).getSchulden()
if sum < 0:
type = 'Schulden'
add = 'Bezahle diese umgehend an den Finanzer.'
else:
type = 'Guthaben'
add = ''
text = MIMEText(
"Hallo {} {},\nDu hast {} im Wert von {:.2f} €. {}\n\nDiese Nachricht wurde automatisch erstellt.".format(
user.firstname, user.lastname, type, abs(sum) / 100, add), 'plain')
debug.debug("subject is {{ {} }}, text is {{ {} }}".format(subject, text.as_string()))
return (subject, text)
def passwordReset(self, user, data):
debug.info("create email passwort reset for user {{ {} }}".format(user))
subject = Header("Password vergessen")
text = MIMEText(
"Hallo {} {},\nDu hast dein Password vergessen!\nDies wurde nun mit Flaschengeist zurückgesetzt.\nDein neues Passwort lautet:\n{}\n\nBitte ändere es sofort in deinem Flaschengeistprolif in https://flaschengeist.wu5.de.".format(
user.firstname, user.lastname, data['password']
), 'plain'
)
debug.debug("subject is {{ {} }}, text is {{ {} }}".format(subject, text.as_string()))
return (subject, text)
def sendMail(self, user, type='credit', jobtransact=None, **kwargs):
debug.info("send email to user {{ {} }}".format(user))
try:
if user.mail == 'None' or not user.mail:
debug.warning("user {{ {} }} has no email-address".format(user))
raise Exception("no valid Email")
msg = MIMEMultipart()
msg['From'] = self.email
msg['To'] = user.mail
if type == 'credit':
subject, text = self.credit(user)
elif type == 'jobtransact':
subject, text = self.jobTransact(user, jobtransact)
elif type == 'jobinvite':
subject, text = self.jobInvite(user, jobtransact)
elif type == 'passwordReset':
subject, text = self.passwordReset(user, kwargs)
else:
raise Exception("Fail to send Email. No type is set. user={}, type={} , jobtransact={}".format(user, type, jobtransact))
msg['Subject'] = subject
msg.attach(text)
debug.debug("send email {{ {} }} to user {{ {} }}".format(msg.as_string(), user))
self.__connect__()
self.smtp.sendmail(self.email, user.mail, msg.as_string())
return {'error': False, 'user': {'userId': user.uid, 'firstname': user.firstname, 'lastname': user.lastname}}
except Exception:
debug.warning("exception in send email", exc_info=True)
return {'error': True, 'user': {'userId': user.uid, 'firstname': user.firstname, 'lastname': user.lastname}}

View File

@ -1,10 +1,9 @@
from werkzeug.exceptions import BadRequest, NotFound
from flaschengeist.system.models.event import EventKind, Event, EventSlot, JobSlot, JobKind
from sqlalchemy.exc import IntegrityError
from flaschengeist import logger
from flaschengeist.system.database import db
from flaschengeist.system.models.event import EventKind, Event, EventSlot, JobSlot, JobKind
def get_event(id):

View File

@ -0,0 +1,14 @@
from flaschengeist.system.models.user import User, Role
from pyhooks import Hook
class Message:
def __init__(self, receiver: User or Role, message: str, subject: str):
self.message = message
self.subject = subject
self.receiver = receiver
@Hook
def send_message(message: Message):
pass

View File

@ -1,6 +1,6 @@
from flask import current_app
from flaschengeist.system.models.user import User
from flaschengeist.system.models.user import User, Role
from flaschengeist.system.database import db
from flaschengeist import logger
@ -44,5 +44,9 @@ def get_users():
return User.query.all()
def get_user_by_role(role: Role):
return User.query.join(User.roles).filter_by(role_id=role.id).all()
def get_user(uid):
return User.query.filter(User.uid == uid).one_or_none()

View File

@ -18,17 +18,18 @@ setup(
"flask_cors",
"werkzeug",
"bjoern",
"python-dateutil"
"python-dateutil",
"pyhooks"
],
extras_require={"ldap": ["flask_ldapconn", "ldap3"]},
entry_points={
"flaschengeist.plugin": [
"auth = flaschengeist.modules.auth:register",
"users = flaschengeist.modules.users:register",
"roles = flaschengeist.modules.roles:register",
"schedule = flaschengeist.modules.schedule:register",
],
"flaschengeist.auth": [
"auth = flaschengeist.modules.auth:AuthRoutePlugin",
"users = flaschengeist.modules.users:UsersPlugin",
"roles = flaschengeist.modules.roles:RolesPlugin",
"schedule = flaschengeist.modules.schedule:SchedulePlugin",
"mail = flaschengeist.modules.message_mail:MailMessagePlugin",
"auth_plain = flaschengeist.modules.auth_plain:AuthPlain",
"auth_ldap = flaschengeist.modules.auth_ldap:AuthLDAP [ldap]",
],