
from __future__ import absolute_import
import inspect
from functools import wraps
import six

OVERRIDE_ARG = "__securityOverride"
SECURE_FIELD = "__secured__"
SECURE_METHOD_NAME = "__is_secure__"


class SecureError(RuntimeError):
    pass


def secured(cls):

    if not callable(cls.__dict__.get(SECURE_METHOD_NAME)):
        raise NotImplementedError("Security method not implemented")

    for name, value in six.iteritems(cls.__dict__):
        if (not inspect.isfunction(value) or
                not getattr(value, SECURE_FIELD, True) or
                name.startswith("__")):
            continue
        setattr(cls, name, _secure_method(value))

    return cls


def unsecured(f):
    setattr(f, SECURE_FIELD, False)
    return f


def _secure_method(method):
    @wraps(method)
    def wrapper(self, *args, **kwargs):
        override = kwargs.pop(OVERRIDE_ARG, False)

        if not (getattr(self, SECURE_METHOD_NAME)() is True or
                override is True):
            raise SecureError("Secured object is not in safe state")

        return method(self, *args, **kwargs)

    return wrapper
