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 werkzeug.exceptions import HTTPException
from . import logger from . import logger
from .modules import AuthPlugin
from .system.config import config, configure_app from .system.config import config, configure_app
from .system.controller import roleController from .system.controller import roleController
@ -30,19 +31,6 @@ class CustomJSONEncoder(JSONEncoder):
return JSONEncoder.default(self, o) 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): def __load_plugins(app):
logger.info('Search for plugins') logger.info('Search for plugins')
app.config['FG_PLUGINS'] = {} app.config['FG_PLUGINS'] = {}
@ -50,10 +38,20 @@ def __load_plugins(app):
logger.debug("Found plugin: >{}<".format(entry_point.name)) logger.debug("Found plugin: >{}<".format(entry_point.name))
plugin = None plugin = None
if config.get(entry_point.name, 'enabled', fallback=False): if config.get(entry_point.name, 'enabled', fallback=False):
plugin = entry_point.load()() plugin = entry_point.load()(config[entry_point.name] if config.has_section(entry_point.name) else {})
app.register_blueprint(plugin.blueprint) if plugin.blueprint:
logger.info("Loaded plugin >{}<".format(entry_point.name)) app.register_blueprint(plugin.blueprint)
app.config["FG_PLUGINS"][entry_point.name] = plugin 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(): def install_all():
@ -77,7 +75,6 @@ def create_app():
from .system.database import db from .system.database import db
configure_app(app) configure_app(app)
db.init_app(app) db.init_app(app)
__load_auth(app)
__load_plugins(app) __load_plugins(app)
@app.route("/", methods=["GET"]) @app.route("/", methods=["GET"])

View File

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

View File

@ -1,5 +1,10 @@
from pyhooks import precall_register
send_message_hook = precall_register("send_message")
class Plugin: class Plugin:
def __init__(self, blueprint, permissions = {}): def __init__(self, config=None, blueprint=None, permissions={}):
self.blueprint = blueprint self.blueprint = blueprint
self.permissions = permissions self.permissions = permissions
@ -10,10 +15,7 @@ class Plugin:
pass pass
class Auth: class AuthPlugin(Plugin):
def configure(self, config):
pass
def login(self, user, pw): def login(self, user, pw):
""" Login routine, MUST BE IMPLEMENTED! """ 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 from flaschengeist.system.controller import accessTokenController, userController
access_controller = LocalProxy(lambda: accessTokenController.AccessTokenController()) access_controller = LocalProxy(lambda: accessTokenController.AccessTokenController())
auth_bp = Blueprint('auth', __name__) auth_bp = Blueprint('auth', __name__)
def register(): class AuthRoutePlugin(Plugin):
return Plugin(auth_bp) def __init__(self, conf):
super().__init__(blueprint=auth_bp)
################################################# #################################################
# Routes # # Routes #
@ -31,84 +31,83 @@ def register():
# DELETE: logout / delete token # # DELETE: logout / delete token #
################################################# #################################################
@auth_bp.route("/auth", methods=['POST'])
def _create_token():
""" Login User
@auth_bp.route("/auth", methods=['POST']) Login in User and create an AccessToken for the User.
def _create_token(): Requires POST data {'userid': string, 'password': string}
""" Login User 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. logger.debug("search user {{ {} }} in database".format(userid))
Requires POST data {'userid': string, 'password': string} user = userController.login_user(userid, password)
Returns: if not user:
A JSON-File with user information and created token or errors raise Unauthorized
""" logger.debug("user is {{ {} }}".format(user))
logger.debug("Start log in.") token = access_controller.create(user, user_agent=request.user_agent)
data = request.get_json() logger.debug("access token is {{ {} }}".format(token))
try: logger.info("User {{ {} }} success login.".format(userid))
userid = data['userid']
password = data['password']
except KeyError:
raise BadRequest("Missing parameter(s)")
logger.debug("search user {{ {} }} in database".format(userid)) # Lets cleanup the DB
user = userController.login_user(userid, password) access_controller.clear_expired()
if not user: return jsonify({"user": user, "token": token, "permissions": user.get_permissions()})
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()})
@auth_bp.route("/auth", methods=['GET']) @auth_bp.route("/auth", methods=['GET'])
@login_required() @login_required()
def _get_tokens(access_token, **kwargs): def _get_tokens(access_token, **kwargs):
tokens = access_controller.get_users_tokens(access_token.user) tokens = access_controller.get_users_tokens(access_token.user)
return jsonify(tokens) return jsonify(tokens)
@auth_bp.route("/auth/<token>", methods=['DELETE']) @auth_bp.route("/auth/<token>", methods=['DELETE'])
@login_required() @login_required()
def _delete_token(token, access_token, **kwargs): def _delete_token(token, access_token, **kwargs):
logger.debug("Try to delete access token {{ {} }}".format(token)) logger.debug("Try to delete access token {{ {} }}".format(token))
token = access_controller.get_token(token, access_token.user) token = access_controller.get_token(token, access_token.user)
if not token: if not token:
logger.debug("Token not found in database!") logger.debug("Token not found in database!")
# Return 403 error, so that users can not bruteforce tokens # Return 403 error, so that users can not bruteforce tokens
# Valid tokens from other users and invalid tokens now are looking the same # Valid tokens from other users and invalid tokens now are looking the same
raise Forbidden raise Forbidden
access_controller.delete_token(token) access_controller.delete_token(token)
access_controller.clear_expired() 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)
return jsonify({"ok": "ok"}) 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 ldap3.utils.hashed import hashed
from werkzeug.exceptions import BadRequest from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA512
from ldap3.core.exceptions import LDAPPasswordIsMandatoryError, LDAPBindError
import flaschengeist.modules as modules
from flask import current_app as app from flask import current_app as app
from flask_ldapconn import LDAPConn from flask_ldapconn import LDAPConn
from ldap3 import SUBTREE, MODIFY_REPLACE, HASHED_SALTED_SHA512 from werkzeug.exceptions import BadRequest
import ssl
from flaschengeist.modules import AuthPlugin
from flaschengeist.system.models.user import User from flaschengeist.system.models.user import User
from flaschengeist import logger
class AuthLDAP(modules.Auth): class AuthLDAP(AuthPlugin):
_default = { def __init__(self, config):
'PORT': '389', super().__init__()
'USE_SSL': 'False'
}
ldap = None
dn = None
def configure(self, config): defaults = {
for name in self._default: 'PORT': '389',
'USE_SSL': 'False'
}
for name in defaults:
if name not in config: if name not in config:
config[name] = self._default[name] config[name] = defaults[name]
app.config.update( app.config.update(
LDAP_SERVER=config['URL'], 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 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.modules import Plugin
from flaschengeist.system.decorator import login_required from flaschengeist.system.decorator import login_required
from flaschengeist.system.controller import roleController from flaschengeist.system.controller import roleController
roles_bp = Blueprint("roles", __name__) roles_bp = Blueprint("roles", __name__)
permissions = {}
def register(): class RolesPlugin(Plugin):
return Plugin(roles_bp, permissions) def __init__(self, config):
super().__init__(config, roles_bp)
###################################################### ######################################################
# Routes # # Routes #
@ -23,66 +23,60 @@ def register():
# DELETE: remove role # # 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']) @roles_bp.route("/roles", methods=['GET'])
@login_required() @login_required()
def __add_role(): def list_roles(self, **kwargs):
data = request.get_json() roles = roleController.get_roles()
if not data or "name" not in data: return jsonify(roles)
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/permissions", methods=['GET'])
@login_required()
def list_permissions(self, **kwargs):
permissions = roleController.get_permissions()
return jsonify(permissions)
@roles_bp.route("/roles", methods=['GET']) @roles_bp.route("/roles/<rid>", methods=['GET'])
@login_required() @login_required()
def __list_roles(**kwargs): def __get_role(self, rid, **kwargs):
roles = roleController.get_roles() role = roleController.get_role(rid)
return jsonify(roles) if role:
return jsonify({
"id": role.id,
@roles_bp.route("/roles/permissions", methods=['GET']) "name": role,
@login_required() "permissions": role.permissions
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:
raise NotFound raise NotFound
data = request.get_json() @roles_bp.route("/roles/<rid>", methods=['PUT'])
if 'name' in data: @login_required()
role.name = data["name"] def __edit_role(self, rid, **kwargs):
if "permissions" in data: role = roleController.get_role(rid)
roleController.set_permissions(role, data["permissions"]) if not role:
roleController.update_role(role) raise NotFound
return jsonify({"ok": "ok"})
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']) @roles_bp.route("/roles/<rid>", methods=['DELETE'])
@login_required() @login_required()
def __delete_role(rid, **kwargs): def __delete_role(self, rid, **kwargs):
if not roleController.delete_role(rid): if not roleController.delete_role(rid):
raise NotFound raise NotFound
return jsonify({"ok": "ok"}) return jsonify({"ok": "ok"})

View File

@ -1,21 +1,20 @@
from dateutil import parser from dateutil import parser
from datetime import datetime, timedelta
from flask import Blueprint, request, jsonify from flask import Blueprint, request, jsonify
from werkzeug.exceptions import BadRequest, NotFound from werkzeug.exceptions import BadRequest, NotFound
from datetime import datetime, timedelta
from flaschengeist.modules import Plugin from flaschengeist.modules import Plugin
from flaschengeist.system.controller import eventController
from flaschengeist.system.database import db from flaschengeist.system.database import db
from flaschengeist.system.decorator import login_required
from flaschengeist.system.models.event import EventKind 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") schedule_bp = Blueprint("schedule", __name__, url_prefix="/schedule")
permissions = {}
def register(): class SchedulePlugin(Plugin):
return Plugin(schedule_bp, permissions) def __init__(self, config):
super().__init__(blueprint=schedule_bp)
#################################################################################### ####################################################################################
# Routes # # Routes #
@ -38,161 +37,159 @@ def register():
# DELETE: remove user # # 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']) @schedule_bp.route("/events", methods=['GET'])
@login_required() # roles=['schedule_read']) @schedule_bp.route("/events/<int:year>/<int:month>", methods=['GET'])
def __get_event(id, **kwargs): @schedule_bp.route("/events/<int:year>/<int:month>/<int:day>", methods=['GET'])
event = eventController.get_event(id) @login_required() # roles=['schedule_read'])
if not event: def __get_events(self, year=datetime.now().year, month=datetime.now().month, day=None, **kwrags):
raise NotFound """Get Event objects for specified date (or month or year),
return jsonify(event) if nothing set then events for current month are returned
Args:
@schedule_bp.route("/events", methods=['GET']) year (int, optional): year to query, defaults to current year
@schedule_bp.route("/events/<int:year>/<int:month>", methods=['GET']) month (int, optional): month to query (if set), defaults to current month
@schedule_bp.route("/events/<int:year>/<int:month>/<int:day>", methods=['GET']) day (int, optional): day to query events for (if set)
@login_required() # roles=['schedule_read']) **kwrags: contains at least access_token (see flaschengeist.decorator)
def __get_events(year=datetime.now().year, month=datetime.now().month, day=None, **kwrags): Returns:
"""Get Event objects for specified date (or month or year), JSON list containing events found
if nothing set then events for current month are returned Raises:
BadRequest: If date is invalid
Args: """
year (int, optional): year to query, defaults to current year try:
month (int, optional): month to query (if set), defaults to current month begin = datetime(year=year, month=month, day=1)
day (int, optional): day to query events for (if set) if day:
**kwrags: contains at least access_token (see flaschengeist.decorator) begin += timedelta(days=day - 1)
Returns: end = begin + timedelta(days=1)
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)
else: 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) events = eventController.get_events(begin, end)
return jsonify(events) return jsonify(events)
except ValueError: except ValueError:
raise BadRequest("Invalid date given") raise BadRequest("Invalid date given")
@schedule_bp.route("/eventKinds", methods=['POST']) @schedule_bp.route("/eventKinds", methods=['POST'])
@login_required() @login_required()
def __new_event_kind(**kwargs): def __new_event_kind(self, **kwargs):
data = request.get_json() data = request.get_json()
if "name" not in data: if "name" not in data:
raise BadRequest raise BadRequest
kind = eventController.create_event_kind(data["name"]) kind = eventController.create_event_kind(data["name"])
return jsonify({"ok": "ok", "id": kind.id}) return jsonify({"ok": "ok", "id": kind.id})
@schedule_bp.route("/slotKinds", methods=["POST"]) @schedule_bp.route("/slotKinds", methods=["POST"])
@login_required() @login_required()
def __new_slot_kind(**kwargs): def __new_slot_kind(self, **kwargs):
data = request.get_json() data = request.get_json()
if not data or "name" not in data: if not data or "name" not in data:
raise BadRequest raise BadRequest
kind = eventController.create_job_kind(data["name"]) kind = eventController.create_job_kind(data["name"])
return jsonify({"ok": "ok", "id": kind.id}) return jsonify({"ok": "ok", "id": kind.id})
@schedule_bp.route("/events", methods=['POST']) @schedule_bp.route("/events", methods=['POST'])
@login_required() @login_required()
def __new_event(**kwargs): def __new_event(self, **kwargs):
data = request.get_json() data = request.get_json()
event = eventController.create_event(begin=parser.isoparse(data["begin"]), event = eventController.create_event(begin=parser.isoparse(data["begin"]),
end=parser.isoparse(data["end"]), end=parser.isoparse(data["end"]),
description=data["description"], description=data["description"],
kind=EventKind.query.get(data["kind"])) kind=EventKind.query.get(data["kind"]))
return jsonify({"ok": "ok", "id": event.id}) return jsonify({"ok": "ok", "id": event.id})
@schedule_bp.route("/events/<int:id>", methods=["DELETE"]) @schedule_bp.route("/events/<int:id>", methods=["DELETE"])
@login_required() @login_required()
def __delete_event(id, **kwargs): def __delete_event(self, id, **kwargs):
if not eventController.delete_event(id): if not eventController.delete_event(id):
raise NotFound raise NotFound
db.session.commit() db.session.commit()
return jsonify({'ok': 'ok'}) return jsonify({'ok': 'ok'})
@schedule_bp.route("/eventKinds/<int:id>", methods=["PUT"]) @schedule_bp.route("/eventKinds/<int:id>", methods=["PUT"])
@login_required() @login_required()
def __edit_event_kind(id, **kwargs): def __edit_event_kind(self, id, **kwargs):
data = request.get_json() data = request.get_json()
if not data or "name" not in data: if not data or "name" not in data:
raise BadRequest raise BadRequest
eventController.rename_event_kind(id, data["name"]) 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):
return jsonify({"ok": "ok"}) return jsonify({"ok": "ok"})
raise NotFound
@schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["PUT"]) @schedule_bp.route("/events/<int:event_id>/slots", methods=["GET"])
@login_required() @login_required()
def __update_slot(event_id, slot_id, **kwargs): def __get_slots(self, event_id, **kwargs):
data = request.get_json() event = eventController.get_event(event_id)
if not data: if not event:
raise BadRequest raise NotFound
return jsonify({event.slots})
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"]) @schedule_bp.route("/events/<int:event_id>/slots/<int:slot_id>", methods=["GET"])
@login_required() @login_required()
def __add_slot(event_id, **kwargs): def __get_slot(self, event_id, slot_id, **kwargs):
event = eventController.get_event(event_id) slot = eventController.get_event_slot(slot_id, event_id)
if not event: if slot:
return jsonify(slot)
raise NotFound 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'} permissions = {'EDIT_USER': 'edit_user'}
def register(): class UsersPlugin(Plugin):
return Plugin(users_bp, permissions) def __init__(self, config):
super().__init__(blueprint=users_bp, permissions=permissions)
################################################# #################################################
# Routes # # Routes #
@ -23,49 +24,45 @@ def register():
# DELETE: remove user # # 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']) @users_bp.route("/users", methods=['GET'])
def __registration(): @login_required()
logger.debug("Register new User...") def __list_users(self, **kwargs):
return jsonify({"ok": "ok... well not implemented"}) logger.debug("Retrieve list of all users")
users = userController.get_users()
return jsonify(users)
@users_bp.route("/users/<uid>", methods=['GET'])
@users_bp.route("/users", methods=['GET']) @login_required()
@login_required() def __get_user(self, uid, **kwargs):
def __list_users(**kwargs): logger.debug("Get information of user {{ {} }}".format(uid))
logger.debug("Retrieve list of all users") user = userController.get_user(uid)
users = userController.get_users() if user:
return jsonify(users) return jsonify(user)
@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:
raise NotFound raise NotFound
if uid != kwargs['access_token'].user.uid and user.has_permissions(permissions['EDIT_USER']): @users_bp.route("/users/<uid>", methods=['PUT'])
return Forbidden @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 uid != kwargs['access_token'].user.uid and user.has_permissions(permissions['EDIT_USER']):
if 'password' not in data: return Forbidden
raise BadRequest("Password is missing")
for key in ["firstname", "lastname", "display_name", "mail"]: data = request.get_json()
if key in data: if 'password' not in data:
setattr(user, key, data[key]) raise BadRequest("Password is missing")
new_password = data['new_password'] if 'new_password' in data else None for key in ["firstname", "lastname", "display_name", "mail"]:
userController.modify_user(user, data['password'], new_password) if key in data:
userController.update_user(user) setattr(user, key, data[key])
return jsonify({"ok": "ok"}) 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 werkzeug.exceptions import BadRequest, NotFound
from flaschengeist.system.models.event import EventKind, Event, EventSlot, JobSlot, JobKind
from sqlalchemy.exc import IntegrityError from sqlalchemy.exc import IntegrityError
from flaschengeist import logger from flaschengeist import logger
from flaschengeist.system.database import db from flaschengeist.system.database import db
from flaschengeist.system.models.event import EventKind, Event, EventSlot, JobSlot, JobKind
def get_event(id): 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 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.system.database import db
from flaschengeist import logger from flaschengeist import logger
@ -44,5 +44,9 @@ def get_users():
return User.query.all() 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): def get_user(uid):
return User.query.filter(User.uid == uid).one_or_none() return User.query.filter(User.uid == uid).one_or_none()

View File

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