# -*- coding: UTF-8 -* """Decorators for permissions, roles and ScoDoc7 Zope compatibility """ import functools from functools import wraps import inspect import flask from flask import g from flask import abort, current_app from flask import request from flask_login import current_user from flask_login import login_required from flask import current_app from werkzeug.exceptions import BadRequest from app.auth.models import Permission def permission_required(permission): def decorator(f): @wraps(f) def decorated_function(*args, **kwargs): current_app.logger.info( "permission_required: %s in %s" % (permission, g.scodoc_dept) ) if not current_user.has_permission(permission, g.scodoc_dept): abort(403) return f(*args, **kwargs) return decorated_function return decorator def admin_required(f): return permission_required(Permission.ScoSuperAdmin)(f) class ZUser(object): "Emulating Zope User" def __init__(self): "create, based on `flask_login.current_user`" self.username = current_user.username def __str__(self): return self.username def has_permission(self, perm, context): """check if this user as the permission `perm` in departement given by `g.scodoc_dept`. """ raise NotImplementedError() class ZRequest(object): "Emulating Zope 2 REQUEST" def __init__(self): self.URL = request.base_url self.URL0 = self.URL self.BASE0 = request.url_root self.QUERY_STRING = request.query_string self.REQUEST_METHOD = request.method self.AUTHENTICATED_USER = current_user if request.method == "POST": self.form = request.form if request.files: # Add files in form: must copy to get a mutable version # request.form is a werkzeug.datastructures.ImmutableMultiDict self.form = self.form.copy() self.form.update(request.files) elif request.method == "GET": self.form = request.args self.RESPONSE = ZResponse() def __str__(self): return """REQUEST URL={r.URL} QUERY_STRING={r.QUERY_STRING} REQUEST_METHOD={r.REQUEST_METHOD} AUTHENTICATED_USER={r.AUTHENTICATED_USER} form={r.form} """.format( r=self ) class ZResponse(object): "Emulating Zope 2 RESPONSE" def __init__(self): self.headers = {} def redirect(self, url): return flask.redirect(url) # http 302 def setHeader(self, header, value): self.headers[header.tolower()] = value def scodoc7func(func): """Décorateur pour intégrer les fonctions Zope 2 de ScoDoc 7. Si on a un kwarg `scodoc_dept`(venant de la route), le stocke dans `g.scodoc_dept`. Ajoute l'argument REQUEST s'il est dans la signature de la fonction. Les paramètres de la query string deviennent des (keywords) paramètres de la fonction. """ @wraps(func) def scodoc7func_decorator(*args, **kwargs): """Decorator allowing legacy Zope published methods to be called via Flask routes without modification. There are two cases: the function can be called 1. via a Flask route ("top level call") 2. or be called directly from Python. If called via a route, this decorator setups a REQUEST object (emulating Zope2 REQUEST) and `g.scodoc_dept` if present in the argument (for routes like `//Scolarite/sco_exemple`). """ assert not args if hasattr(g, "zrequest"): top_level = False else: g.zrequest = None top_level = True # if "scodoc_dept" in kwargs: g.scodoc_dept = kwargs["scodoc_dept"] del kwargs["scodoc_dept"] elif not hasattr(g, "scodoc_dept"): # if toplevel call g.scodoc_dept = None # --- Emulate Zope's REQUEST REQUEST = ZRequest() g.zrequest = REQUEST req_args = REQUEST.form # args from query string (get) or form (post) # --- Add positional arguments pos_arg_values = [] # PY3 à remplacer par inspect.getfullargspec en py3: argspec = inspect.getargspec(func) current_app.logger.info("argspec=%s" % str(argspec)) nb_default_args = len(argspec.defaults) if argspec.defaults else 0 if nb_default_args: arg_names = argspec.args[:-nb_default_args] else: arg_names = argspec.args for arg_name in arg_names: if arg_name == "REQUEST": # special case pos_arg_values.append(REQUEST) else: pos_arg_values.append(req_args[arg_name]) current_app.logger.info("pos_arg_values=%s" % pos_arg_values) # Add keyword arguments if nb_default_args: for arg_name in argspec.args[-nb_default_args:]: if arg_name == "REQUEST": # special case kwargs[arg_name] = REQUEST elif arg_name in req_args: # set argument kw optionnel kwargs[arg_name] = req_args[arg_name] current_app.logger.info( "scodoc7func_decorator: top_level=%s, pos_arg_values=%s, kwargs=%s" % (top_level, pos_arg_values, kwargs) ) value = func(*pos_arg_values, **kwargs) if not top_level: return value else: # Build response, adding collected http headers: headers = [] kw = {"response": value, "status": 200} if g.zrequest: headers = g.zrequest.RESPONSE.headers if not headers: # no customized header, speedup: return value if "content-type" in headers: kw["mimetype"] = headers["content-type"] r = flask.Response(**kw) for h in headers: r.headers[h] = headers[h] return r return scodoc7func_decorator # Le "context" de ScoDoc7 class ScoDoc7Context(object): """Context object for legacy Zope methods. Mainly used to call published methods, as context.function(...) """ def __init__(self, globals_dict): self.__dict__ = globals_dict