Exemple #1
0
def put_loader(*args, serializer):
    """
    Decorator to automatically load and update a model from json request data

    :param serializer: The ModelSerializer to use to load data from the request
    """
    def wrapped(fn):
        @wraps(fn)
        def decorated(*args, **kwargs):
            try:
                result = serializer.load(request.get_json(),
                                         instance=kwargs.pop('instance'))
            except ValidationError as v:
                errors = v.messages
                result = v.valid_data
            else:
                errors = None
            # TODO: When to abort the request?
            #abort(HTTPStatus.NOT_FOUND)
            return fn(result, errors)

        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapped(args[0])
    return wrapped
Exemple #2
0
    def serializer(self, *args, many=False):
        """Decorator to wrap a :class:`~backend.api.ModelSerializer` class,
        registering the wrapped serializer as the specific one to use for the
        serializer's model.

         For example::

            from backend.extensions.api import api
            from backend.api import ModelSerializer
            from models import Foo

            @api.serializer  # @api.serializer() works too
            class FooSerializer(ModelSerializer):
                class Meta:
                    model = Foo

            @api.serializer(many=True)
            class FooListSerializer(ModelSerializer):
                class Meta:
                    model = Foo
         """
        def decorator(serializer_class):
            model_name = serializer_class.Meta.model.__name__
            self.deferred_serializers.append(
                (model_name, serializer_class, many))
            return serializer_class

        if was_decorated_without_parenthesis(args):
            return decorator(args[0])
        return decorator
Exemple #3
0
def attach_events(*args):
    """Class decorator for SQLAlchemy models to attach listeners on class
    methods decorated with :func:`.on`

    Usage::

        @attach_events
        class User(Model):
            email = Column(String(50))

            @on('email', 'set')
            def lowercase_email(self, new_value, old_value, initiating_event):
                self.email = new_value.lower()
    """
    def wrapper(cls):
        for name, fn in cls.__dict__.items():
            if not name.startswith('__') and hasattr(fn,
                                                     _SQLAlchemyEvent.ATTR):
                e = getattr(fn, _SQLAlchemyEvent.ATTR)
                if e.field_name:
                    event.listen(getattr(cls, e.field_name), e.event_name, fn,
                                 **e.listen_kwargs)
                else:
                    event.listen(cls, e.event_name, fn, **e.listen_kwargs)
        return cls

    if was_decorated_without_parenthesis(args):
        return wrapper(args[0])
    return wrapper
Exemple #4
0
def auth_required(*args, **kwargs):
    """Decorator for requiring an authenticated user, optionally with roles
    Roles are passed as keyword arguments, like so:
    @auth_required(role='REQUIRE_THIS_ONE_ROLE')
    @auth_required(roles=['REQUIRE', 'ALL', 'OF', 'THESE', 'ROLES'])
    @auth_required(one_of=['EITHER_THIS_ROLE', 'OR_THIS_ONE'])
    One of role or roles kwargs can also be combined with one_of:
    @auth_required(role='REQUIRED', one_of=['THIS', 'OR_THIS'])
    # equivalent, but more clearly describing the resultant behavior:
    @auth_required(role='REQUIRED', and_one_of=['THIS', 'OR_THIS'])
    Aborts with HTTP 401: Unauthorized if no user is logged in, or
    HTTP 403: Forbidden if any of the specified role checks fail
    """
    required_roles = []
    one_of_roles = []
    if not was_decorated_without_parenthesis(args):
        if 'role' in kwargs and 'roles' in kwargs:
            raise RuntimeError(
                'can only pass one of `role` or `roles` kwargs to auth_required'
            )
        elif 'role' in kwargs:
            required_roles = [kwargs['role']]
        elif 'roles' in kwargs:
            required_roles = kwargs['roles']

        if 'one_of' in kwargs and 'and_one_of' in kwargs:
            raise RuntimeError(
                'can only pass one of `one_of` or `and_one_of` kwargs to auth_required'
            )
        elif 'one_of' in kwargs:
            one_of_roles = kwargs['one_of']
        elif 'and_one_of' in kwargs:
            one_of_roles = kwargs['and_one_of']

    def wrapper(fn):
        @wraps(fn)
        @flask_security_auth_required('session', 'token')
        @roles_required(*required_roles)
        @roles_accepted(*one_of_roles)
        def decorated(*args, **kwargs):
            return fn(*args, **kwargs)

        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapper(args[0])
    return wrapper
Exemple #5
0
def param_converter(*decorator_args, **decorator_kwargs):
    """
    Call with the url parameter names as keyword argument keys, their values
    being the model to convert to.

    Models will be looked up by the url param names. If a url param name
    is prefixed with the snake-cased model name, the prefix will be stripped.

    If a model isn't found, abort with a 404.

    The action's argument names must match the snake-cased model names.

    For example::

        @bp.route('/users/<int:user_id>/posts/<int:id>')
        @param_converter(user_id=User, id=Post)
        def show_post(user, post):
            # the param converter does the database lookups:
            # user = User.query.filter_by(id=user_id).first()
            # post = Post.query.filter_by(id=id).first()
            # and calls the decorated action: show_post(user, post)

        # or to customize the argument names passed to the action:
        @bp.route('/users/<int:user_id>/posts/<int:post_id>')
        @param_converter(user_id={'user_arg_name': User},
                         post_id={'post_arg_name': Post})
        def show_post(user_arg_name, post_arg_name):
            # do stuff ...

    Also supports parsing arguments from the query string. For query string
    keyword arguments, use a lookup (dict, Enum) or callable::

        @bp.route('/users/<int:id>')
        @param_converter(id=User, foo=str, optional=int)
        def show_user(user, foo, optional=10):
            # GET /users/1?foo=bar
            # calls show_user(user=User.get(1), foo='bar')
    """
    def wrapped(fn):
        @wraps(fn)
        def decorated(*view_args, **view_kwargs):
            try:
                view_kwargs = _convert_models(view_kwargs, decorator_kwargs)
                view_kwargs = _convert_query_params(
                    view_kwargs, decorator_kwargs)
                return fn(*view_args, **view_kwargs)
            except HTTPException as e:
                raise e
            except Exception:
                if apm.is_initialized:
                    apm.capture_exception()
                traceback.print_exc()
                abort(HTTPStatus.INTERNAL_SERVER_ERROR)
        return decorated

    if was_decorated_without_parenthesis(decorator_args):
        return wrapped(decorator_args[0])
    return wrapped
def auth_required_same_user(*args, **kwargs):
    """Decorator for requiring an authenticated user to be the same as the
    user in the URL parameters. By default the user url parameter name to
    lookup is 'id', but this can be customized by passing an argument:

    @auth_require_same_user('user_id')
    @bp.route('/users/<int:user_id>/foo/<int:id>')
    def get(user_id, id):
        # do stuff

    Any keyword arguments are passed along to the @auth_required decorator,
    so roles can also be specified in the same was as it, eg:
    @auth_required_same_user('user_id', role='ROLE_ADMIN')

    Aborts with HTTP 403: Forbidden if the user-check fails
    """
    auth_kwargs = {}
    user_id_parameter_name = 'id'
    if not was_decorated_without_parenthesis(args):
        auth_kwargs = kwargs
        if args and isinstance(args[0], str):
            user_id_parameter_name = args[0]

    def wrapper(fn):
        @wraps(fn)
        @auth_required(**auth_kwargs)
        def decorated(*args, **kwargs):
            try:
                user_id = request.view_args[user_id_parameter_name]
            except KeyError:
                raise KeyError('Unable to find the user lookup parameter '
                               f'{user_id_parameter_name} in the url args')
            if not Permission(UserNeed(user_id)).can():
                abort(HTTPStatus.FORBIDDEN)
            return fn(*args, **kwargs)
        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapper(args[0])
    return wrapper
Exemple #7
0
def post_loader(*args, serializer):
    """
    Decorator to automatically instantiate a model from json request data

    :param serializer: The ModelSerializer to use to load data from the request
    """
    def wrapped(fn):
        @wraps(fn)
        def decorated(*args, **kwargs):
            return fn(*serializer.load(request.get_json()))
        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapped(args[0])
    return wrapped
Exemple #8
0
def list_loader(*args, model):
    """
    Decorator to automatically query the database for all records of a model.

    :param model: The model class to query
    """
    def wrapped(fn):
        @wraps(fn)
        def decorated(*args, **kwargs):
            return fn(model.query.all())
        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapped(args[0])
    return wrapped
Exemple #9
0
def anonymous_user_required(*args, **kwargs):
    """Decorator requiring no user be logged in
    Aborts with HTTP 403: Forbidden if there is an authenticated user
    """
    def wrapper(fn):
        @wraps(fn)
        def decorated(*args, **kwargs):
            if current_user.is_authenticated:
                abort(HTTPStatus.FORBIDDEN)
            return fn(*args, **kwargs)

        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapper(args[0])

    return wrapper
Exemple #10
0
def put_loader(*args, serializer):
    """
    Decorator to automatically load and update a model from json request data

    :param serializer: The ModelSerializer to use to load data from the request
    """
    def wrapped(fn):
        @wraps(fn)
        def decorated(*args, **kwargs):
            result = serializer.load(request.get_json(),
                                     instance=kwargs.pop('instance'))
            if not result.errors and not result.data.id:
                abort(HTTPStatus.NOT_FOUND)
            return fn(*result)
        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapped(args[0])
    return wrapped
Exemple #11
0
def post_loader(*args, serializer):
    """
    Decorator to automatically instantiate a model from json request data

    :param serializer: The ModelSerializer to use to load data from the request
    """
    def wrapped(fn):
        @wraps(fn)
        def decorated(*args, **kwargs):
            try:
                result = serializer.load(request.get_json())
            except ValidationError as v:
                errors = v.messages
                result = v.valid_data
            else:
                errors = None
            return fn(result, errors)

        return decorated

    if was_decorated_without_parenthesis(args):
        return wrapped(args[0])
    return wrapped