Exemple #1
0
    def __init__(self,
                 threshold=500,
                 default_timeout=300,
                 ignore_errors=False):
        BaseCache.__init__(self, default_timeout=default_timeout)
        CachelibSimpleCache.__init__(self,
                                     threshold=threshold,
                                     default_timeout=default_timeout)

        self.ignore_errors = ignore_errors
Exemple #2
0
    def __init__(self,
                 app=None,
                 *,
                 base_url=None,
                 client_id=None,
                 client_secret=None,
                 cache=None,
                 scope='openid',
                 url_prefix='/oauth2',
                 session_key='flask-auth0-cookie'):

        # :param app: Flask app
        # :param base_url: your identity provider's base url
        # :param client_id: CLIENT_ID
        # :param client_secret: CLIENT_SECRET
        # :param cache: a cache object that implements `werkzeug.contrib.cache.BaseCache` to store the tokens in
        # :param scope: oauth2 scopes
        # :param url_prefix: what should be the prefix of all the routes needed for oauth2. default = /oauth2
        # :param session_key: name of the key used in the session to store the uid

        self.app = app
        self.user_agent = f"{__name__}/{__version__} python-requests/{requests.__version__}"

        self.base_url = base_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.scope = scope
        self.url_prefix = url_prefix
        self.session_key = session_key

        # User actions
        self._after_login_handler = None
        self._after_logout_handler = None
        self._after_refresh_handler = None

        self._logger = None

        # Setup backend cache
        if cache is None:
            self.cache = SimpleCache()
        elif isinstance(cache, BaseCache):
            self.cache = cache
        else:
            raise ValueError(
                f'your backend cache must implement `cachelib.BaseCache`. {cache} is {type(cache)}'
            )

        # Utilities
        self.signer = None
        self.hasher = None

        self.openid_config = None

        if app is not None:
            self.init_app(app)
Exemple #3
0
 def __init__(self,
              app=None,
              route=None,
              blueprint=None,
              stream_cache=None,
              path='templates.yaml'):
     self.app = app
     self._route = route
     self._intent_view_funcs = {}
     self._intent_converts = {}
     self._intent_defaults = {}
     self._intent_mappings = {}
     self._launch_view_func = None
     self._session_ended_view_func = None
     self._on_session_started_callback = None
     self._default_intent_view_func = None
     self._player_request_view_funcs = {}
     self._player_mappings = {}
     self._player_converts = {}
     if app is not None:
         self.init_app(app, path)
     elif blueprint is not None:
         self.init_blueprint(blueprint, path)
     if stream_cache is None:
         self.stream_cache = SimpleCache()
     else:
         self.stream_cache = stream_cache
Exemple #4
0
 def _simple(self, **kwargs):
     """Returns a :class:`SimpleCache` instance
     .. warning::
         This cache system might not be thread safe. Use with caution.
     """
     kwargs.update(dict(threshold=self._config('threshold', 500)))
     return SimpleCache(**kwargs)
def test_dynamic_permission_needs_cache_invalidation(app, dynamic_permission):
    """Testing DynamicPermission refreshes needs.

    This is important when protecting a view with
    @permission.require(http_exception=403)
    If cache does not get invalidated, the needs will only be refreshed when
    the Python process restarts.
    """
    cache = SimpleCache()
    InvenioAccess(app, cache=cache)
    with app.test_request_context():
        user_can_all = User(email='*****@*****.**')
        user_can_open = User(email='*****@*****.**')
        db.session.add(user_can_all)
        db.session.add(user_can_open)

        db.session.add(ActionUsers(action='open', user=user_can_all))
        db.session.flush()

        permission_open = dynamic_permission(ActionNeed('open'))

        assert permission_open.needs == set([Need(method='id', value=1)])

        db.session.add(ActionUsers(action='open', user=user_can_open))
        db.session.flush()

        assert permission_open.needs == set(
            [Need(method='id', value=1), Need(method='id', value=2)]
        )
def test_invenio_access_permission_cache(app, dynamic_permission):
    """Caching the user using memory caching."""
    cache = SimpleCache()
    InvenioAccess(app, cache=cache)
    with app.test_request_context():
        user_can_all = User(email='*****@*****.**')
        user_can_open = User(email='*****@*****.**')
        user_can_open_1 = User(email='*****@*****.**')

        db.session.add(user_can_all)
        db.session.add(user_can_open)
        db.session.add(user_can_open_1)

        db.session.add(ActionUsers(action='open', user=user_can_all))

        db.session.flush()

        permission_open = dynamic_permission(ActionNeed('open'))

        identity_open = FakeIdentity(UserNeed(user_can_open.id))

        assert not permission_open.allows(identity_open)
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1)]),
            set([])
        )

        db.session.add(ActionUsers(action='open', user=user_can_open))
        db.session.flush()

        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_open)
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )

        db.session.add(ActionUsers(action='open', argument=1,
                                   user=user_can_open_1))
        db.session.flush()

        identity_open_1 = FakeIdentity(UserNeed(user_can_open_1.id))
        permission_open_1 = dynamic_permission(
            ParameterizedActionNeed('open', '1'))
        assert not permission_open.allows(identity_open_1)
        assert permission_open_1.allows(identity_open_1)
        assert current_access.get_action_cache('open::1') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2),
                 Need(method='id', value=3)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )
Exemple #7
0
    def init_app (self, app):
        app.config.from_object (Config)

        for layer in app.config['TILE_LAYERS']:
            if 'map_style' in layer:
                renderers[layer['id']] = Render (app, layer)

        app.tile_cache = SimpleCache (
            threshold = TILE_CACHE_SIZE,
            default_timeout = TILE_CACHE_TIMEOUT
        )
Exemple #8
0
    def decorator(func):
        @wraps(func)
        def decorated(*args, **kwargs):
            cache_id = key(*args, **kwargs)
            return_value = decorated.cache.get(cache_id)

            if return_value is None:
                return_value = func(*args, **kwargs)
                decorated.cache.set(
                    cache_id,
                    return_value if return_value is not None else NO_DATA,
                    timeout=timeout)
            elif return_value == NO_DATA:
                return_value = None

            return return_value

        decorated.cache = SimpleCache()
        return decorated
Exemple #9
0
def simple(config, *args, **kwargs):
    defaults = dict(gen_defaults('threshold', **config))
    defaults.update(kwargs)
    return SimpleCache(*args, **defaults)
 def _factory(self, *args, **kwargs):
     return SimpleCache(*args, **kwargs)
Exemple #11
0
    "postgresql": "postgresql+psycopg2",
    "mysql": "mysql",
    "sqllite": "sqllite"
}

METADATA_DB_DEFAULT_PORTS = {"postgresql": 5432, "mysql": 3306}

BROKER_PREFIXES = {"redis": "redis", "rabbitmq": "pyamqp"}

BROKER_DEFAULT_PORTS = {"redis": 6379, "rabbitmq": 5672}

SUPERSET_RESULTS_BACKENDS = {
    "simple":
    lambda: SimpleCache(threshold=get_env(
        "SUPERSET_SIMPLE_RESULTS_BACKEND_THRESHOLD", default=10, cast=int),
                        default_timeout=get_env(
                            "SUPERSET_SIMPLE_RESULTS_BACKEND_DEFAULT_TIMEOUT",
                            default=300,
                            cast=float)),
    "redis":
    lambda: RedisCache(
        host=get_env("SUPERSET_REDIS_RESULTS_BACKEND_HOST"),
        port=get_env(
            "SUPERSET_REDIS_RESULTS_BACKEND_PORT", default=6379, cast=int),
        password=get_env("SUPERSET_REDIS_RESULTS_BACKEND_PASSWORD"),
        key_prefix=get_env("SUPERSET_REDIS_RESULTS_BACKEND_KEY_PREFIX",
                           default="superset_results"),
        db=get_env("SUPERSET_REDIS_RESULTS_BACKEND_DB", default=0, cast=int),
        default_timeout=get_env(
            "SUPERSET_REDIS_RESULTS_BACKEND_DEFAULT_TIMEOUT",
            default=300,
            cast=float)),
Exemple #12
0
def simple(app, config, args, kwargs):
    kwargs.update(dict(threshold=config['CACHE_THRESHOLD']))
    return SimpleCache(*args, **kwargs)
Exemple #13
0
class JwtManager:  # pylint: disable=too-many-instance-attributes
    """Manages the JWT verification and JWKS key lookup."""

    ALGORITHMS = 'RS256'

    def __init__(self, app=None):
        """Initialize the JWTManager instance."""
        # These are all set in the init_app function, but are listed here for easy reference
        self.app = app
        self.well_known_config = None
        self.well_known_obj_cache = None
        self.algorithms = JwtManager.ALGORITHMS
        self.jwks_uri = None
        self.issuer = None
        self.audience = None
        self.client_secret = None
        self.cache = None
        self.caching_enabled = False

        self.jwt_oidc_test_mode = False
        self.jwt_oidc_test_keys = None

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        """Initialize this extension.

        if the config['JWT_OIDC_WELL_KNOWN_CONFIG'] is set, then try to load the JWKS_URI & ISSUER from that
        If it is not set
        attempt to load the JWKS_URI and ISSUE from the application config

        Required settings to function:
        WELL_KNOWN_CONFIG (optional) is this is set, the JWKS_URI & ISSUER will be loaded from there
        JWKS_URI: the endpoint defined for the jwks_keys
        ISSUER: the endpoint for the issuer of the tokens
        ALGORITHMS: only RS256 is supported
        AUDIENCE: the oidc audience (or API_IDENTIFIER)
        CLIENT_SECRET: the shared secret / key assigned to the client (audience)
        """
        self.app = app
        self.jwt_oidc_test_mode = app.config.get('JWT_OIDC_TEST_MODE', None)
        #
        # CHECK IF WE"RE RUNNING IN TEST_MODE!!
        #
        if self.jwt_oidc_test_mode:
            app.logger.debug(
                'JWT MANAGER running in test mode, using locally defined certs & tokens'
            )

            self.issuer = app.config.get('JWT_OIDC_TEST_ISSUER',
                                         'localhost.localdomain')
            self.jwt_oidc_test_keys = app.config.get('JWT_OIDC_TEST_KEYS',
                                                     None)
            self.audience = app.config.get('JWT_OIDC_TEST_AUDIENCE', None)
            self.client_secret = app.config.get('JWT_OIDC_TEST_CLIENT_SECRET',
                                                None)
            self.jwt_oidc_test_private_key_pem = app.config.get(
                'JWT_OIDC_TEST_PRIVATE_KEY_PEM', None)

            if self.jwt_oidc_test_keys:
                app.logger.debug('local key being used: {}'.format(
                    self.jwt_oidc_test_keys))
            else:
                app.logger.error(
                    'Attempting to run JWT Manager with no local key assigned')
                raise Exception(
                    'Attempting to run JWT Manager with no local key assigned')

        else:

            self.algorithms = app.config.get(
                'JWT_OIDC_ALGORITHMS', JwtManager.ALGORITHMS).replace(' ', '')\
                .split(',')

            # If the WELL_KNOWN_CONFIG is set, then go fetch the JWKS & ISSUER
            self.well_known_config = app.config.get(
                'JWT_OIDC_WELL_KNOWN_CONFIG', None)
            if self.well_known_config:
                # try to get the jwks & issuer from the well known config
                # jurl = urlopen(url=self.well_known_config, context=ssl.SSLContext()) # for gangster testing
                jurl = urlopen(url=self.well_known_config)
                self.well_known_obj_cache = json.loads(
                    jurl.read().decode('utf-8'))

                self.jwks_uri = self.well_known_obj_cache['jwks_uri']
                self.issuer = self.well_known_obj_cache['issuer']
            else:

                self.jwks_uri = app.config.get('JWT_OIDC_JWKS_URI', None)
                self.issuer = app.config.get('JWT_OIDC_ISSUER', None)

            # Setup JWKS caching
            self.caching_enabled = app.config.get('JWT_OIDC_CACHING_ENABLED',
                                                  False)
            if self.caching_enabled:
                self.cache = SimpleCache(default_timeout=app.config.get(
                    'JWT_OIDC_JWKS_CACHE_TIMEOUT', 300))

            self.audience = app.config.get('JWT_OIDC_AUDIENCE', None)
            self.client_secret = app.config.get('JWT_OIDC_CLIENT_SECRET', None)

        app.logger.debug('JWKS_URI: {}'.format(self.jwks_uri))
        app.logger.debug('ISSUER: {}'.format(self.issuer))
        app.logger.debug('ALGORITHMS: {}'.format(self.algorithms))
        app.logger.debug('AUDIENCE: {}'.format(self.audience))
        app.logger.debug('CLIENT_SECRET: {}'.format(self.client_secret))
        app.logger.debug('JWT_OIDC_TEST_MODE: {}'.format(
            self.jwt_oidc_test_mode))
        app.logger.debug('JWT_OIDC_TEST_KEYS: {}'.format(
            self.jwt_oidc_test_keys))

        # set the auth error handler
        auth_err_handler = app.config.get('JWT_OIDC_AUTH_ERROR_HANDLER',
                                          JwtManager.handle_auth_error)
        app.register_error_handler(AuthError, auth_err_handler)

        app.teardown_appcontext(self.teardown)

    def teardown(self, exception):
        """Remove any module items.

        This is a flask extension lifecycle hook.
        """
        # ctx = _app_ctx_stack.top
        # if hasattr(ctx, 'cached object'):

    @staticmethod
    def handle_auth_error(ex):
        """Error handler."""
        response = jsonify(ex.error)
        response.status_code = ex.status_code
        return response

    @staticmethod
    def get_token_auth_header():
        """Obtain the access token from the Authorization Header."""
        auth = request.headers.get('Authorization', None)
        if not auth:
            raise AuthError(
                {
                    'code': 'authorization_header_missing',
                    'description': 'Authorization header is expected'
                }, 401)

        parts = auth.split()

        if parts[0].lower() != 'bearer':
            raise AuthError(
                {
                    'code': 'invalid_header',
                    'description':
                    'Authorization header must start with Bearer'
                }, 401)

        if len(parts) < 2:
            raise AuthError(
                {
                    'code': 'invalid_header',
                    'description': 'Token not found after Bearer'
                }, 401)

        if len(parts) > 2:
            raise AuthError(
                {
                    'code':
                    'invalid_header',
                    'description':
                    'Authorization header is an invalid token structure'
                }, 401)

        return parts[1]

    @staticmethod
    def _get_token_auth_cookie():
        """Obtain the access token from the cookie."""
        cookie_name = current_app.config.get('JWT_OIDC_AUTH_COOKIE_NAME',
                                             'oidc-jwt')
        cookie = request.cookies.get(cookie_name, None)
        if not cookie:
            raise AuthError(
                {
                    'code': 'authorization_cookie_missing',
                    'description': 'Authorization cookie is expected'
                }, 401)

        return cookie

    def contains_role(self, roles):
        """Check that the listed roles are in the token using the registered callback.

        Args:
            roles [str,]: Comma separated list of valid roles
            JWT_ROLE_CALLBACK (fn): The callback added to the Flask configuration
        """
        token = self.get_token_auth_header()
        unverified_claims = jwt.get_unverified_claims(token)
        roles_in_token = current_app.config['JWT_ROLE_CALLBACK'](
            unverified_claims)
        if any(elem in roles_in_token for elem in roles):
            return True
        return False

    def has_one_of_roles(self, roles):
        """Check that at least one of the roles are in the token using the registered callback.

        Args:
            roles [str,]: Comma separated list of valid roles
            JWT_ROLE_CALLBACK (fn): The callback added to the Flask configuration
        """
        def decorated(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                self._require_auth_validation(*args, **kwargs)
                if self.contains_role(roles):
                    return f(*args, **kwargs)
                raise AuthError(
                    {
                        'code':
                        'missing_a_valid_role',
                        'description':
                        'Missing a role required to access this endpoint'
                    }, 401)

            return wrapper

        return decorated

    def validate_roles(self, required_roles):
        """Check that the listed roles are in the token using the registered callback.

        Args:
            required_roles [str,]: Comma separated list of required roles
            JWT_ROLE_CALLBACK (fn): The callback added to the Flask configuration
        """
        token = self.get_token_auth_header()
        unverified_claims = jwt.get_unverified_claims(token)
        roles_in_token = current_app.config['JWT_ROLE_CALLBACK'](
            unverified_claims)
        if all(elem in roles_in_token for elem in required_roles):
            return True
        return False

    def requires_roles(self, required_roles):
        """Check that the listed roles are in the token using the registered callback.

        Args:
            required_roles [str,]: Comma separated list of required roles
            JWT_ROLE_CALLBACK (fn): The callback added to the Flask configuration
        """
        def decorated(f):
            @wraps(f)
            def wrapper(*args, **kwargs):
                self._require_auth_validation(*args, **kwargs)
                if self.validate_roles(required_roles):
                    return f(*args, **kwargs)
                raise AuthError(
                    {
                        'code':
                        'missing_required_roles',
                        'description':
                        'Missing the role(s) required to access this endpoint'
                    }, 401)

            return wrapper

        return decorated

    def requires_auth(self, f):
        """Validate the Bearer Token."""
        @wraps(f)
        def decorated(*args, **kwargs):

            self._require_auth_validation(*args, **kwargs)

            return f(*args, **kwargs)

        return decorated

    def requires_auth_cookie(self, f):
        """Validate the Cookie."""
        @wraps(f)
        def decorated(*args, **kwargs):
            self._require_auth_cookie_validation(*args, **kwargs)

            return f(*args, **kwargs)

        return decorated

    def _require_auth_validation(self, *args, **kwargs):  # pylint: disable=unused-argument
        token = self.get_token_auth_header()
        self._validate_token(token)

    def _require_auth_cookie_validation(self, *args, **kwargs):  # pylint: disable=unused-argument
        token = self._get_token_auth_cookie()
        self._validate_token(token)

    def _validate_token(self, token):
        try:
            unverified_header = jwt.get_unverified_header(token)
        except jwt.JWTError as jerr:
            raise AuthError(
                {
                    'code':
                    'invalid_header',
                    'description':
                    'Invalid header. '
                    'Use an RS256 signed JWT Access Token'
                }, 401) from jerr
        if unverified_header['alg'] == 'HS256':
            raise AuthError(
                {
                    'code':
                    'invalid_header',
                    'description':
                    'Invalid header. '
                    'Use an RS256 signed JWT Access Token'
                }, 401)
        if 'kid' not in unverified_header:
            raise AuthError(
                {
                    'code': 'invalid_header',
                    'description': 'Invalid header. '
                    'No KID in token header'
                }, 401)

        rsa_key = self.get_rsa_key(self.get_jwks(), unverified_header['kid'])

        if not rsa_key and self.caching_enabled:
            # Could be key rotation, invalidate the cache and try again
            self.cache.delete('jwks')
            rsa_key = self.get_rsa_key(self.get_jwks(),
                                       unverified_header['kid'])

        if not rsa_key:
            raise AuthError(
                {
                    'code': 'invalid_header',
                    'description':
                    'Unable to find jwks key referenced in token'
                }, 401)

        try:
            payload = jwt.decode(token,
                                 rsa_key,
                                 algorithms=self.algorithms,
                                 audience=self.audience,
                                 issuer=self.issuer)
            _request_ctx_stack.top.current_user = g.jwt_oidc_token_info = payload
        except jwt.ExpiredSignatureError as sig:
            raise AuthError(
                {
                    'code': 'token_expired',
                    'description': 'token has expired'
                }, 401) from sig
        except jwt.JWTClaimsError as jwe:
            raise AuthError(
                {
                    'code':
                    'invalid_claims',
                    'description':
                    'incorrect claims,'
                    ' please check the audience and issuer'
                }, 401) from jwe
        except Exception as exc:
            raise AuthError(
                {
                    'code': 'invalid_header',
                    'description': 'Unable to parse authentication'
                    ' token.'
                }, 401) from exc

    def get_jwks(self):
        """Return the test, cached or fetched JWKS for the KID provided."""
        if self.jwt_oidc_test_mode:
            return self.jwt_oidc_test_keys

        if self.caching_enabled:
            return self._get_jwks_from_cache()
        return self._fetch_jwks_from_url()

    def _get_jwks_from_cache(self):
        jwks = self.cache.get('jwks')
        if jwks is None:
            jwks = self._fetch_jwks_from_url()
            self.cache.set('jwks', jwks)
        return jwks

    def _fetch_jwks_from_url(self):
        jsonurl = urlopen(self.jwks_uri)
        return json.loads(jsonurl.read().decode('utf-8'))

    def create_jwt(self, claims, header):
        """Create a token for the client and JWKS kid provided."""
        token = jwt.encode(claims,
                           self.jwt_oidc_test_private_key_pem,
                           headers=header,
                           algorithm='RS256')
        return token

    @staticmethod
    def get_rsa_key(jwks, kid):
        """Return the matching RSA key for kid, from the jwks array."""
        rsa_key = {}
        for key in jwks['keys']:
            if key['kid'] == kid:
                rsa_key = {
                    'kty': key['kty'],
                    'kid': key['kid'],
                    'use': key['use'],
                    'n': key['n'],
                    'e': key['e']
                }
        return rsa_key
def test_invenio_access_permission_cache_users_updates(
    app,
    dynamic_permission
):
    """Testing ActionUsers cache with inserts/updates/deletes."""
    cache = SimpleCache()
    InvenioAccess(app, cache=cache)
    with app.test_request_context():
        # Creation of some data to test.
        user_1 = User(email='*****@*****.**')
        user_2 = User(email='*****@*****.**')
        user_3 = User(email='*****@*****.**')
        user_4 = User(email='*****@*****.**')
        user_5 = User(email='*****@*****.**')
        user_6 = User(email='*****@*****.**')

        db.session.add(user_1)
        db.session.add(user_2)
        db.session.add(user_3)
        db.session.add(user_4)
        db.session.add(user_5)
        db.session.add(user_6)

        db.session.add(ActionUsers(action='open', user=user_1))
        db.session.add(ActionUsers(action='write', user=user_4))

        db.session.flush()

        # Creation identities to test.
        identity_user_1 = FakeIdentity(UserNeed(user_1.id))
        identity_user_2 = FakeIdentity(UserNeed(user_2.id))
        identity_user_3 = FakeIdentity(UserNeed(user_3.id))
        identity_user_4 = FakeIdentity(UserNeed(user_4.id))
        identity_user_5 = FakeIdentity(UserNeed(user_5.id))
        identity_user_6 = FakeIdentity(UserNeed(user_6.id))

        # Test if user 1 can open. In this case, the cache should store only
        # this object.
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_user_1)
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1)]),
            set([])
        )

        # Test if user 4 can write. In this case, the cache should have this
        # new object and the previous one (Open is allowed to user_1)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_user_4)
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=4)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1)]),
            set([])
        )

        # If we add a new user to the action open, the open action in cache
        # should be removed but it should still containing the write entry.
        db.session.add(ActionUsers(action='open', user=user_2))
        db.session.flush()
        assert current_access.get_action_cache('open') is None
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_user_2)
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=4)]),
            set([])
        )

        # Test if the new user is added to the action 'open'
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_user_4)
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=4)]),
            set([])
        )

        # If we update an action swapping a user, the cache containing the
        # action, should be removed.
        user_4_action_write = ActionUsers.query.filter(
            ActionUsers.action == 'write' and
            ActionUsers.user == user_4).first()
        user_4_action_write.user = user_3
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )

        # Test if the user_3 can now write.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_user_4)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_user_3)
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=3)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )

        # If we remove a user from an action, the cache should clear the
        # action item.
        user_3_action_write = ActionUsers.query.filter(
            ActionUsers.action == 'write' and
            ActionUsers.user == user_3).first()
        db.session.delete(user_3_action_write)
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        # If no one is allowed to perform an action then everybody is allowed.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_user_3)
        assert current_access.get_action_cache('write') == (
            set([]),
            set([])
        )
        db.session.add(ActionUsers(action='write', user=user_5))
        db.session.flush()
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_user_5)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_user_3)
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=5)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )

        # If you update the name of an existing action, the previous action
        # and the new action should be remove from cache.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_user_5)
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=5)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2)]),
            set([])
        )
        user_5_action_write = ActionUsers.query.filter(
            ActionUsers.action == 'write' and
            ActionUsers.user == user_5).first()
        user_5_action_write.action = 'open'
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        assert current_access.get_action_cache('open') is None
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_user_1)
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2),
                 Need(method='id', value=5)]),
            set([])
        )
        db.session.add(ActionUsers(action='write', user=user_4))
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_user_5)
        assert current_access.get_action_cache('write') == (
            set([Need(method='id', value=4)]),
            set([])
        )

        db.session.add(ActionUsers(action='open', argument='1', user=user_6))
        db.session.flush()
        permission_open_1 = dynamic_permission(
            ParameterizedActionNeed('open', '1'))
        assert not permission_open.allows(identity_user_6)
        assert permission_open_1.allows(identity_user_6)
        assert current_access.get_action_cache('open::1') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2),
                 Need(method='id', value=5),
                 Need(method='id', value=6)]),
            set([])
        )
        user_6_action_open_1 = ActionUsers.query.filter_by(
            action='open', argument='1', user_id=user_6.id).first()
        user_6_action_open_1.argument = '2'
        db.session.flush()
        assert current_access.get_action_cache('open::1') is None
        assert current_access.get_action_cache('open::2') is None
        permission_open_2 = dynamic_permission(
            ParameterizedActionNeed('open', '2'))
        assert permission_open_2.allows(identity_user_6)
        assert current_access.get_action_cache('open::2') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2),
                 Need(method='id', value=5),
                 Need(method='id', value=6)]),
            set([])
        )
        # open action cache should remain as before
        assert current_access.get_action_cache('open') == (
            set([Need(method='id', value=1),
                 Need(method='id', value=2),
                 Need(method='id', value=5)]),
            set([])
        )
CACHE : Cache
"""

CACHE_DEFAULT_TIMEOUT = 60 * 24 * 7
"""
Cache responses timeout.

CACHE_DEFAULT_TIMEOUT : int
"""

CACHE.init_app(APP)

APP.config.update(
    COMPRESS_LEVEL=3,
    COMPRESS_CACHE_KEY=lambda x: x.full_path,
    COMPRESS_CACHE_BACKEND=lambda: SimpleCache(default_timeout=  # noqa
                                               CACHE_DEFAULT_TIMEOUT),
)

Compress(APP)

IMAGES_DIRECTORY = os.path.join(os.getcwd(), 'static', 'images')
"""
Images directory.

IMAGES_DIRECTORY : unicode
"""


def _null_to_None(data):
    """
    Converts *Javascript* originated *null* and *undefined* strings
def test_invenio_access_permission_cache_system_roles_updates(
    app,
    dynamic_permission
):
    """Testing ActionSystemRoles cache with inserts/updates/deletes."""
    # This test case is doing the same of user test case but using
    # system roles.
    cache = SimpleCache()
    InvenioAccess(app, cache=cache)
    with app.test_request_context():
        system_role_1 = SystemRoleNeed('system_role_1')
        system_role_2 = SystemRoleNeed('system_role_2')
        system_role_3 = SystemRoleNeed('system_role_3')
        system_role_4 = SystemRoleNeed('system_role_4')
        system_role_5 = SystemRoleNeed('system_role_5')
        system_role_6 = SystemRoleNeed('system_role_6')
        current_access.system_roles = {
            'system_role_1': system_role_1,
            'system_role_2': system_role_2,
            'system_role_3': system_role_3,
            'system_role_4': system_role_4,
            'system_role_5': system_role_5,
            'system_role_6': system_role_6,
        }

        # Creation of some data to test.
        db.session.add(ActionSystemRoles(action='open',
                                         role_name=system_role_1.value))
        db.session.add(ActionSystemRoles(action='write',
                                         role_name=system_role_4.value))

        db.session.flush()

        # Creation of identities to test.
        identity_fake_1 = FakeIdentity(system_role_1)
        identity_fake_2 = FakeIdentity(system_role_2)
        identity_fake_3 = FakeIdentity(system_role_3)
        identity_fake_4 = FakeIdentity(system_role_4)
        identity_fake_5 = FakeIdentity(system_role_5)
        identity_fake_6 = FakeIdentity(system_role_6)

        # Test if system_role_1 can open. In this case, the cache should store
        # only this object.
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_fake_1)
        assert current_access.get_action_cache('open') == (
            set([system_role_1]),
            set([])
        )

        # Test if system_role_2 can write. In this case, the cache should
        # have this new object and the previous one (Open is allowed to
        # system_role_1)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_4)
        assert current_access.get_action_cache('write') == (
            set([system_role_4]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([system_role_1]),
            set([])
        )

        # If we add a new system role to the action open, the open action in
        # cache should be removed but it should still containing the write
        # entry.
        db.session.add(ActionSystemRoles(action='open',
                                         role_name=system_role_2.value))
        db.session.flush()
        assert current_access.get_action_cache('open') is None
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_fake_2)
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2]),
            set([])
        )
        assert current_access.get_action_cache('write') == (
            set([system_role_4]),
            set([])
        )

        # Test if the new role is added to the action 'open'
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_4)
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2]),
            set([])
        )
        assert current_access.get_action_cache('write') == (
            set([system_role_4]),
            set([])
        )

        # If we update an action swapping a role, the cache containing the
        # action, should be removed.
        role_4_action_write = ActionSystemRoles.query.filter(
            ActionSystemRoles.action == 'write' and
            ActionSystemRoles.role_name == system_role_4.value).first()
        role_4_action_write.role_name = system_role_3.value
        db.session.flush()

        assert current_access.get_action_cache('write') is None
        assert current_access.get_action_cache('open') is not None
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2]),
            set([])
        )

        # Test if the system_role_3 can write now.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_fake_4)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_3)
        assert current_access.get_action_cache('write') == (
            set([system_role_3]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2]),
            set([])
        )

        # If we remove a role from an action, the cache should clear the
        # action item.
        cust_action_write = ActionSystemRoles.query.filter(
            ActionSystemRoles.action == 'write' and
            ActionSystemRoles.role_name == system_role_3).first()
        db.session.delete(cust_action_write)
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        # If no one is allowed to perform an action then everybody is allowed.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_3)
        assert current_access.get_action_cache('write') == (
            set([]),
            set([])
        )
        db.session.add(ActionSystemRoles(action='write',
                                         role_name=system_role_5.value))
        db.session.flush()
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_5)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_fake_3)
        assert current_access.get_action_cache('write') == (
            set([system_role_5]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2]),
            set([])
        )

        # If you update the name of an existing action, the previous action
        # and the new action should be remove from cache.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_5)
        assert current_access.get_action_cache('write') == (
            set([system_role_5]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2]),
            set([])
        )
        role_5_action_write = ActionSystemRoles.query.filter(
            ActionSystemRoles.action == 'write' and
            ActionSystemRoles.role_name == system_role_5.value).first()
        role_5_action_write.action = 'open'
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        assert current_access.get_action_cache('open') is None
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_fake_1)
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2, system_role_5]),
            set([])
        )
        db.session.add(ActionSystemRoles(action='write',
                                         role_name=system_role_4.value))
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_fake_5)
        assert current_access.get_action_cache('write') == (
            set([system_role_4]),
            set([])
        )

        db.session.add(ActionSystemRoles(action='open', argument='1',
                                         role_name=system_role_6.value))
        db.session.flush()
        permission_open_1 = dynamic_permission(
            ParameterizedActionNeed('open', '1'))
        assert not permission_open.allows(identity_fake_6)
        assert permission_open_1.allows(identity_fake_6)
        assert current_access.get_action_cache('open::1') == (
            set([system_role_1, system_role_2, system_role_5, system_role_6]),
            set([])
        )
        user_6_action_open_1 = ActionSystemRoles.query.filter_by(
            action='open', argument='1', role_name=system_role_6.value).first()
        user_6_action_open_1.argument = '2'
        db.session.flush()
        assert current_access.get_action_cache('open::1') is None
        assert current_access.get_action_cache('open::2') is None
        permission_open_2 = dynamic_permission(
            ParameterizedActionNeed('open', '2'))
        assert permission_open_2.allows(identity_fake_6)
        assert current_access.get_action_cache('open::2') == (
            set([system_role_1, system_role_2, system_role_5, system_role_6]),
            set([])
        )
        # open action cache should remain as before
        assert current_access.get_action_cache('open') == (
            set([system_role_1, system_role_2, system_role_5]),
            set([])
        )
def test_invenio_access_permission_cache_roles_updates(
    app,
    dynamic_permission
):
    """Testing ActionRoles cache with inserts/updates/deletes."""
    # This test case is doing the same of user test case but using roles.
    cache = SimpleCache()
    InvenioAccess(app, cache=cache)
    with app.test_request_context():
        # Creation of some data to test.
        role_1 = Role(name='role_1')
        role_2 = Role(name='role_2')
        role_3 = Role(name='role_3')
        role_4 = Role(name='role_4')
        role_5 = Role(name='role_5')
        role_6 = Role(name='role_6')

        db.session.add(role_1)
        db.session.add(role_2)
        db.session.add(role_3)
        db.session.add(role_4)
        db.session.add(role_5)
        db.session.add(role_6)

        db.session.add(ActionRoles(action='open', role=role_1))
        db.session.add(ActionRoles(action='write', role=role_4))

        db.session.flush()

        # Creation of identities to test.
        identity_fake_role_1 = FakeIdentity(RoleNeed(role_1.name))
        identity_fake_role_2 = FakeIdentity(RoleNeed(role_2.name))
        identity_fake_role_3 = FakeIdentity(RoleNeed(role_3.name))
        identity_fake_role_4 = FakeIdentity(RoleNeed(role_4.name))
        identity_fake_role_5 = FakeIdentity(RoleNeed(role_5.name))
        identity_fake_role_6 = FakeIdentity(RoleNeed(role_6.name))

        # Test if role 1 can open. In this case, the cache should store only
        # this object.
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_fake_role_1)
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name)]),
            set([])
        )

        # Test if role 4 can write. In this case, the cache should have this
        # new object and the previous one (Open is allowed to role_1)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_role_4)
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_4.name)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name)]),
            set([])
        )

        # If we add a new role to the action open, the open action in cache
        # should be removed but it should still containing the write entry.
        db.session.add(ActionRoles(action='open', role=role_2))
        db.session.flush()
        assert current_access.get_action_cache('open') is None
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_fake_role_2)
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name)]),
            set([])
        )
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_4.name)]),
            set([])
        )

        # Test if the new role is added to the action 'open'
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_role_4)
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name)]),
            set([])
        )
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_4.name)]),
            set([])
        )

        # If we update an action swapping a role, the cache containing the
        # action, should be removed.
        role_4_action_write = ActionRoles.query.filter(
            ActionRoles.action == 'write' and
            ActionRoles.role == role_4).first()
        role_4_action_write.role = role_3
        db.session.flush()

        assert current_access.get_action_cache('write') is None
        assert current_access.get_action_cache('open') is not None
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name)]),
            set([])
        )

        # Test if the role_3 can write now.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_fake_role_4)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_role_3)
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_3.name)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name)]),
            set([])
        )

        # If we remove a role from an action, the cache should clear the
        # action item.
        role_3_action_write = ActionRoles.query.filter(
            ActionRoles.action == 'write' and
            ActionRoles.role == role_3).first()
        db.session.delete(role_3_action_write)
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        # If no one is allowed to perform an action then everybody is allowed.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_role_3)
        assert current_access.get_action_cache('write') == (
            set([]),
            set([])
        )
        db.session.add(ActionRoles(action='write', role=role_5))
        db.session.flush()
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_role_5)
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_fake_role_3)
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_5.name)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name)]),
            set([])
        )

        # If you update the name of an existing action, the previous action
        # and the new action should be remove from cache.
        permission_write = dynamic_permission(ActionNeed('write'))
        assert permission_write.allows(identity_fake_role_5)
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_5.name)]),
            set([])
        )
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name)]),
            set([])
        )
        role_5_action_write = ActionRoles.query.filter(
            ActionRoles.action == 'write' and
            ActionRoles.role == role_5).first()
        role_5_action_write.action = 'open'
        db.session.flush()
        assert current_access.get_action_cache('write') is None
        assert current_access.get_action_cache('open') is None
        permission_open = dynamic_permission(ActionNeed('open'))
        assert permission_open.allows(identity_fake_role_1)
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name),
                 Need(method='role', value=role_5.name)]),
            set([])
        )
        db.session.add(ActionRoles(action='write', role=role_4))
        permission_write = dynamic_permission(ActionNeed('write'))
        assert not permission_write.allows(identity_fake_role_5)
        assert current_access.get_action_cache('write') == (
            set([Need(method='role', value=role_4.name)]),
            set([])
        )

        db.session.add(ActionRoles(action='open', argument='1', role=role_6))
        db.session.flush()
        permission_open_1 = dynamic_permission(
            ParameterizedActionNeed('open', '1'))
        assert not permission_open.allows(identity_fake_role_6)
        assert permission_open_1.allows(identity_fake_role_6)
        assert current_access.get_action_cache('open::1') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name),
                 Need(method='role', value=role_5.name),
                 Need(method='role', value=role_6.name)]),
            set([])
        )
        user_6_action_open_1 = ActionRoles.query.filter_by(
            action='open', argument='1', role_id=role_6.id).first()
        user_6_action_open_1.argument = '2'
        db.session.flush()
        assert current_access.get_action_cache('open::1') is None
        assert current_access.get_action_cache('open::2') is None
        permission_open_2 = dynamic_permission(
            ParameterizedActionNeed('open', '2'))
        assert permission_open_2.allows(identity_fake_role_6)
        assert current_access.get_action_cache('open::2') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name),
                 Need(method='role', value=role_5.name),
                 Need(method='role', value=role_6.name)]),
            set([])
        )
        # open action cache should remain as before
        assert current_access.get_action_cache('open') == (
            set([Need(method='role', value=role_1.name),
                 Need(method='role', value=role_2.name),
                 Need(method='role', value=role_5.name)]),
            set([])
        )
Exemple #18
0
if "WEBFAF_ENVIRON_PRODUCTION" in os.environ:
    app.config.from_object("webfaf.config.ProductionConfig")
elif "WEBFAF_ENVIRON_TEST" in os.environ:
    app.config.from_object("webfaf.config.TestingConfig")
else:
    app.config.from_object("webfaf.config.DevelopmentConfig")

db = SQLAlchemy(app)

if app.config["CACHE_TYPE"].lower() == "memcached":
    flask_cache = MemcachedCache(['{0}:{1}'.format(
        app.config["MEMCACHED_HOST"],
        app.config["MEMCACHED_PORT"])],
                                 key_prefix=app.config["MEMCACHED_KEY_PREFIX"])
elif app.config["CACHE_TYPE"].lower() == "simple":
    flask_cache = SimpleCache()
else:
    flask_cache = NullCache()

if app.config["PROXY_SETUP"]:
    app.wsgi_app = ProxyFix(app.wsgi_app)

if app.config["OPENID_ENABLED"]:
    from flask_openid import OpenID
    from openid_teams import teams
    oid = OpenID(app, safe_roots=[], extension_responses=[teams.TeamsResponse])
    from webfaf.login import login # pylint: disable=cyclic-import
    app.register_blueprint(login)
    from webfaf.user import user # pylint: disable=cyclic-import
    app.register_blueprint(user)
def werkzeug_subscriber():
    subscriber = Subscriber(SQLite3SubscriberStorage('subscriber.db'),
                            WerkzeugCacheTempSubscriberStorage(SimpleCache()))
    yield from subscriber_app(subscriber)
def test_intenio_access_cache_performance(app, dynamic_permission):
    """Performance test simulating 1000 users."""
    InvenioAccess(app, cache=None)
    with app.test_request_context():
        # CDS has (2015-11-19) 74 actions with 414 possible arguments with
        # 49259 users and 307 roles. In this test we are going to divide
        # into 50 and use the next prime number.
        users_number = 991
        actions_users_number = 11
        actions_roles_number = 7

        roles = []
        actions = []
        for i in range(actions_roles_number):
            role = Role(name='role{0}'.format(i))
            roles.append(role)
            db.session.add(role)
            db.session.flush()

            action_role = ActionRoles(
                action='action{0}'.format(
                    str(i % actions_roles_number)),
                role=role)
            actions.append(action_role)
            db.session.add(action_role)
            db.session.flush()

        users = []
        for i in range(users_number):
            user = User(
                email='invenio{0}@inveniosoftware.org'.format(str(i)),
                roles=[roles[i % actions_roles_number]])
            users.append(user)
            db.session.add(user)
            db.session.flush()

            action_user = ActionUsers(action='action{0}'.format(
                str((i % actions_users_number) + actions_roles_number)),
                user=user)
            actions.append(action_user)
            db.session.add(action_user)
            db.session.flush()

        def test_permissions():
            """Iterates over all users checking its permissions."""
            for i in range(users_number):
                identity = FakeIdentity(UserNeed(users[i].id))

                # Allowed permission
                permission_allowed_both = dynamic_permission(
                    ActionNeed('action{0}'.format(
                        (i % actions_users_number) +
                        actions_roles_number)),
                    ActionNeed('action{0}'.format(i % actions_roles_number))
                )
                assert permission_allowed_both.allows(identity)

                # Not allowed action user
                permission_not_allowed_user = dynamic_permission(
                    ActionNeed('action{0}'.format(
                        (i + 1) % actions_users_number +
                        actions_roles_number))
                )
                assert not permission_not_allowed_user.allows(identity)

                # Not allowed action role
                permission_not_allowed_role = dynamic_permission(
                    ActionNeed('action{0}'.format(
                        (i + 1) % actions_roles_number))
                )
                assert not permission_not_allowed_role.allows(identity)

        app.extensions['invenio-access'].cache = None
        start_time_wo_cache = time.time()
        test_permissions()
        end_time_wo_cache = time.time()
        time_wo_cache = end_time_wo_cache - start_time_wo_cache

        app.extensions['invenio-access'].cache = SimpleCache()
        start_time_w_cache = time.time()
        test_permissions()
        end_time_w_cache = time.time()
        time_w_cache = end_time_w_cache - start_time_w_cache

        assert time_wo_cache / time_w_cache > 10
Exemple #21
0
import datetime
from cachelib import SimpleCache
from app.clients.sms import PollableSMSClient
from app.clients.sms.utils import timed
from saplivelink365 import Configuration, ApiClient, AuthorizationApi, SMSV20Api

# See https://livelink.sapmobileservices.com/documentation/guides/sms-channel/delivery_statuses/#body-description
sap_response_map = {
    'SENT': 'sending',
    'DELIVERED': 'delivered',
    'RECEIVED': 'delivered',
    'ERROR': 'failed',
}

auth_cache = SimpleCache()


def get_sap_responses(status):
    return sap_response_map[status]


class SAPSMSClient(PollableSMSClient):
    def __init__(self, client_id=None, client_secret=None, *args, **kwargs):
        super(SAPSMSClient, self).__init__(*args, **kwargs)
        self._client_id = client_id
        self._client_secret = client_secret

    def init_app(self, logger, notify_host, *args, **kwargs):
        self.logger = logger
        self.notify_host = notify_host
Exemple #22
0
class AuthorizationCodeFlow(object):
    def __init__(self,
                 app=None,
                 *,
                 base_url=None,
                 client_id=None,
                 client_secret=None,
                 cache=None,
                 scope='openid',
                 url_prefix='/oauth2',
                 session_key='flask-auth0-cookie'):

        # :param app: Flask app
        # :param base_url: your identity provider's base url
        # :param client_id: CLIENT_ID
        # :param client_secret: CLIENT_SECRET
        # :param cache: a cache object that implements `werkzeug.contrib.cache.BaseCache` to store the tokens in
        # :param scope: oauth2 scopes
        # :param url_prefix: what should be the prefix of all the routes needed for oauth2. default = /oauth2
        # :param session_key: name of the key used in the session to store the uid

        self.app = app
        self.user_agent = f"{__name__}/{__version__} python-requests/{requests.__version__}"

        self.base_url = base_url
        self.client_id = client_id
        self.client_secret = client_secret
        self.scope = scope
        self.url_prefix = url_prefix
        self.session_key = session_key

        # User actions
        self._after_login_handler = None
        self._after_logout_handler = None
        self._after_refresh_handler = None

        self._logger = None

        # Setup backend cache
        if cache is None:
            self.cache = SimpleCache()
        elif isinstance(cache, BaseCache):
            self.cache = cache
        else:
            raise ValueError(
                f'your backend cache must implement `cachelib.BaseCache`. {cache} is {type(cache)}'
            )

        # Utilities
        self.signer = None
        self.hasher = None

        self.openid_config = None

        if app is not None:
            self.init_app(app)

    def init_app(self, app):
        # Config of the extension
        self.client_id = app.config.setdefault('AUTH0_CLIENT_ID',
                                               self.client_id)
        self.client_secret = app.config.setdefault('AUTH0_CLIENT_SECRET',
                                                   self.client_secret)
        self.base_url = app.config.setdefault('AUTH0_BASE_URL', self.base_url)
        self.scope = app.config.setdefault('AUTH0_SCOPE', self.scope)

        if self.client_id is None or self.client_secret is None or self.base_url is None:
            raise ValueError('Missing config variables ')

        self.session_key = app.config.setdefault('AUTH0_SESSION_KEY',
                                                 self.session_key)
        self.url_prefix = app.config.setdefault('AUTH0_URL_PREFIX',
                                                self.url_prefix)

        # Setup some crypto stuff.
        # I am not a cryptographer and might do this wrong. For prod use, tweak appropriately.
        # If you don't know why or what into you should change these values,
        # google for "Key Derivation Function" or ask someone with a Security background
        key_material = scrypt(password=app.secret_key.encode(),
                              salt=b'flask-auth0',
                              dklen=32,
                              n=256,
                              r=8,
                              p=1)

        # Utils
        self.signer = URLSafeSerializer(key_material[:16])
        self.hasher = lru_cache(
            maxsize=256
        )(  # Because this is non-trivial, we cache already computes values
            lambda value, uid: blake2b(
                value, key=key_material[16:], person=uid, digest_size=32).
            digest())

        self._logger = app.logger
        self.openid_config = OpenIDConfig(self.base_url)

        # Routes
        blueprint = Blueprint('flask-auth0', __name__)

        blueprint.add_url_rule('/login', 'login', self.login)
        blueprint.add_url_rule('/logout', 'logout', self.logout)
        blueprint.add_url_rule('/callback', 'callback', self.callback)

        app.register_blueprint(blueprint=blueprint, url_prefix=self.url_prefix)

    def protected(self, enforce=False):
        """
        This decorator indicates that the auth info needs to be available in the decorated route
        :param enforce: returns 401 if the auth info is not available
        :return:
        """
        def actual_decorator(func):
            @wraps(func)
            def decorated_function(*args, **kwargs):
                if enforce:
                    if self.is_authenticated:
                        return func(*args, **kwargs)
                    raise Unauthorized()
                return func(*args, **kwargs)

            return decorated_function

        return actual_decorator

    # Three decorators to make it easy for actions to trigger after login, logout, refresh
    def after_login(self, func):
        self._after_login_handler = func
        return func

    def after_logout(self, func):
        self._after_logout_handler = func
        return func

    def after_refresh(self, func):
        self._after_refresh_handler = func
        return func

    # Bunch of useful properties
    @property
    def is_authenticated(self):
        # If there's an access token, we consider the user logged in
        return self.access_token is not None

    @property
    def is_refreshable(self):
        # Checks whether there is a refresh token
        return session.get(self.session_key) is not None and self.cache.has(
            self._make_key('refresh_token'))

    @property
    def access_token(self):
        return self.cache.get(self._make_key('access_token'))

    @property
    def token_type(self):
        return self.cache.get(self._make_key('token_type'))

    @property
    def authorization_header(self):
        # Returns the value that should go in the Authorization header
        # when you want to use the access_token
        # This is a convenience function
        return f'{self.token_type} {self.access_token}'

    @property
    def id_token(self):
        return self.cache.get(self._make_key('id_token'))

    @property
    def refresh_token(self):
        return self.cache.get(self._make_key('refresh_token'))

    @lru_cache(maxsize=1024)
    def get_verified_claims(self, id_token):
        # Converts the jwt id_token into verified claims
        try:
            # We can get the info in the id_token, but it needs to be verified
            u_header, u_claims = jwt.get_unverified_header(
                id_token), jwt.get_unverified_claims(id_token)

            # Get the key which was used to sign this id_token
            kid, alg = u_header['kid'], u_header['alg']

        except JWTError:
            self._logger.warn('Tried to verify claims of a broken id_token')
            return {}

        else:
            # Obtain JWT and the keys to validate the signature
            try:
                jwks_response = requests.get(
                    self.openid_config.jwks_uri,
                    headers={
                        'User-Agent': self.user_agent,
                    },
                )
            except requests.HTTPError:
                return {}
            else:
                jwks_data = jwks_response.json()
                try:
                    for key in jwks_data['keys']:
                        if key['kid'] == kid:
                            payload = jwt.decode(
                                token=id_token,
                                key=key,
                                audience=self.client_id,
                                issuer=self.openid_config.issuer)
                            return payload
                except ExpiredSignatureError:
                    self._logger.warn(
                        'Tried to verify claims of an expired id_token')

        return {}

    # Retrieves the OpenID userinfo_endpoint
    def get_user_info(self):
        self._logger.debug('get_user_info() was called')

        try:
            result = requests.get(self.openid_config.userinfo_url,
                                  headers={
                                      'Authorization':
                                      self.authorization_header,
                                      'Content-Type': 'application/json'
                                  })
            result.raise_for_status()
        except requests.HTTPError:
            return {}
        else:
            return result.json()

    def logout_url(self, return_to=None):

        if return_to is None:
            return_to = request.args.get(
                'return_to') or request.referrer or '/'

        # Redirect to auth0 logout
        params = {
            'returnTo': return_to or url_for('index', _external=True),
            'client_id': self.client_id
        }

        return f'{self.openid_config.issuer}v2/logout?{urlencode(params)}'

    # Route definitions
    def logout(self, return_to=None):
        """
        Handler for logging out a user.
        This clears the server-side session entry and redirects to the index endpoint
        :return: redirect()
        """
        self._logger.debug('logout() was called')

        self.cache.delete_many(
            self._make_key('token_data'),
            self._make_key('refresh_token'),
        )
        session.clear()

        if callable(self._after_logout_handler):
            self._after_logout_handler()
            self._logger.debug('after_logout_handler() was called')

        return redirect(self.logout_url(return_to=return_to))

    def login_url(self,
                  return_to=None,
                  prompt='login',
                  response_type='code',
                  redirect_uri=None):

        # Returns the full url for doing a login
        if prompt not in {'none', 'login', 'consent', 'select_account'}:
            raise ValueError('Invalid prompt')
        if response_type not in {'code', 'token'}:
            raise ValueError('Invalid response_type')

        if return_to is None:
            return_to = request.args.get(
                'return_to') or request.referrer or '/'

        if redirect_uri is None:
            redirect_uri = url_for('flask-auth0.callback', _external=True)

        query_parameters = {
            'response_type': response_type,
            'scope': self.scope,
            'state': self.signer.dumps({'return_to': return_to}),
            'client_id': self.client_id,
            'prompt': prompt,
            # Not strictly necessary to include redirect_uri, but avoids potential issues
            'redirect_uri': redirect_uri,
        }

        return f'{self.openid_config.authorization_url}?{urlencode(query_parameters)}'

    def login(self,
              return_to=None,
              prompt='login',
              response_type='code',
              redirect_uri=None):
        """
        Handler for logging in a user.
        This provides a redirect to the authorization url
        :return: redirect()
        """
        self._logger.debug('login() was called')
        return redirect(
            self.login_url(return_to=return_to,
                           prompt=prompt,
                           response_type=response_type,
                           redirect_uri=redirect_uri))

    def callback(self):
        """
        Handler for the OAuth2 callback
        This gets the code and turns it into tokens
        :return: redirect()
        """
        self._logger.debug('callback() was called')

        try:  # to get the state
            state = self.signer.loads(request.args.get('state'))
        except BadSignature:  # State has been tampered with
            return BadRequest(description="State could not be validated")
        except TypeError:  # state is None, not in the url
            state = {}

        # Handle callback errors
        error = request.args.get('error')
        error_description = request.args.get('error_description')
        error_uri = request.args.get('error_uri')

        if error is not None:
            return self.callback_error_handler(
                error=error,
                error_description=error_description,
                error_uri=error_uri,
                state=state)

        code = request.args.get('code')
        if code is not None:  # try to login using the code in the url arg

            # Get the tokens using the code
            token_result = requests.post(self.openid_config.token_url,
                                         data={
                                             'code':
                                             request.args.get('code'),
                                             'grant_type':
                                             'authorization_code',
                                             'client_id':
                                             self.client_id,
                                             'client_secret':
                                             self.client_secret,
                                             'redirect_uri':
                                             url_for('flask-auth0.callback',
                                                     _external=True)
                                         })

            try:
                token_data = token_result.json()

                # Handle errors in access token request
                error = token_data.get('error')
                error_description = token_data.get('error_description')
                error_uri = token_data.get('error_uri')
                if error is not None:
                    return self.access_token_request_error_handler(
                        error=error,
                        error_description=error_description,
                        error_uri=error_uri)

                # Raise for other HTTP trouble
                token_result.raise_for_status()

            except requests.HTTPError as e:
                self._logger.error(
                    f'Could not exchange code for access_token: {e}')
                raise InternalServerError(
                    description=f"Could not obtain access_token with code")

            else:
                session[self.session_key] = uuid.uuid4()
                self._update_tokens(**token_data)

            # Execute user actions
            if callable(self._after_login_handler):
                self._logger.debug('after_login_handler() was called')
                self._after_login_handler()

            # Get return url from the state
            return_to = state.get('return_to')
            if return_to:
                self._logger.debug(f'Returning to {return_to}')
                return redirect(return_to)

            # Fall back on a default
            return Response('Login Successful', status=200)

        else:  # No code in url, return an error
            return Unauthorized(description="Unauthorized")

    def verify_email(self):
        email = request.args.get('email')
        message = request.args.get('message')
        success = request.args.get('success')

    # Convenience functions
    def refresh(self):
        """
        Handler for the OAuth2 token refresh
        This exchanges the refresh_token for a new access_token
        :return: None
        """
        self._logger.debug('refresh() was called')

        token_result = requests.post(
            self.openid_config.token_url,
            data={
                'grant_type': 'refresh_token',
                'scope': self.scope,
                'client_id': self.client_id,
                'client_secret': self.client_secret,
                'refresh_token': self.refresh_token,
            },
        )
        # TODO: better error handling (see callback)
        token_result.raise_for_status()
        token_data = token_result.json()

        self._update_tokens(**token_data)

        if callable(self._after_refresh_handler):
            self._after_refresh_handler()
            self._logger.debug('after_refresh_handler() was called')

    # Logic for updating the cache with new token info
    def _update_tokens(self,
                       *,
                       access_token,
                       token_type='Bearer',
                       refresh_token=None,
                       id_token=None,
                       expires_in=3600,
                       **kwargs):

        self._logger.debug('update_tokens() was called')

        if kwargs:
            self._logger.debug(f'got extra token data: {kwargs.keys()}')

        if id_token is not None:
            self.cache.set(self._make_key('id_token'),
                           id_token,
                           timeout=expires_in)

        # Update the cache for the next request
        self.cache.set(self._make_key('access_token'),
                       access_token,
                       timeout=expires_in)

        self.cache.set(self._make_key('token_type'),
                       token_type,
                       timeout=expires_in)

        # Handle the refresh_token if present
        # it doesn't have a timeout since the refresh token shouldn't expire
        if refresh_token is not None:
            self.cache.set(
                self._make_key('refresh_token'),
                refresh_token,
            )

    # To further obfuscate the relation between the cache key and the session id,
    # the token itself is cryptographically hashed, and that hash is used as the key in the backend cache
    # Is this strictly necessary to make it work? No, but it seemed like a cool thing to do :)
    def _make_key(self, value: str):
        uid = session.setdefault(self.session_key, uuid.uuid4())
        return self.hasher(value=value.encode(), uid=uid.bytes)

    @property
    def session_id(self):
        return session.get(self.session_key)

    def callback_error_handler(self,
                               error,
                               error_description=None,
                               error_uri=None,
                               state=None):

        self._logger.error(f'{error}: {error_description} {error_uri} {state}')

        error_mapping = {
            'invalid_request': BadRequest,  # 400
            'unauthorized': Unauthorized,  # 401
            'unauthorized_client': Unauthorized,  # 401
            'access_denied': Forbidden,  # 403
            'unsupported_response_type': Forbidden,  # 403
            'invalid_scope': Forbidden,  # 403
            'server_error': InternalServerError,  # 500
            'temporarily_unavailable': ServiceUnavailable,  # 503
        }

        HTTPError = error_mapping.get(error, BadRequest)  # 400
        raise HTTPError(description=error_description)

    def access_token_request_error_handler(self,
                                           error,
                                           error_description=None,
                                           error_uri=None):

        self._logger.error(f'{error}: {error_description} {error_uri}')

        if error == 'invalid_scope':
            pass

        if error == 'unsupported_grant_type':
            pass

        if error == 'unauthorized_client':
            pass

        if error == 'invalid_grant':
            pass

        if error == 'invalid_client':
            pass

        if error == 'invalid_request':
            pass

        raise InternalServerError(
            description="error trying to obtain access_token")
Exemple #23
0
# (c) 2009 Haoyu Bai (http://gaedav.google.com/).
"""
Implement cache mechanism.
"""
import logging
import threading
from builtins import object

from cachelib import MemcachedCache, RedisCache, SimpleCache

try:
    memcache3 = MemcachedCache()
    # memcache3 = RedisCache()
except Exception as e:
    logging.info(e)
    memcache3 = SimpleCache()

memcache3._stats = {
    "byte_hits": 0,
    "bytes": 0,
    "hits": 0,
    "items": 0,
    "misses": 0,
    "oldest_item_age": 0,
    # keep track of operations other than get (= hits + misses)
    "set": 0,
    "set_multi": 0,
    "delete": 0,
    "get_list": 0,
    "set_list": 0,
    "del_list": 0,
Exemple #24
0
from . import filters
from .db import db, db_reload

BACKCOMPAT_SERIES_ALIASES = {
    'brno': 'brno-pyvo',
    'praha': 'praha-pyvo',
    'ostrava': 'ostrava-pyvo',
    'olomouc': 'olomouc-pyvo',
    'plzen': 'plzen-pyvo',
    'liberec': 'liberec-pyvo',
}

# Be careful when using SimpleCache!
# See: http://werkzeug.pocoo.org/docs/contrib/cache/
#      #werkzeug.contrib.cache.SimpleCache
cache = SimpleCache(threshold=500, default_timeout=300)

routes = []


def route(url, methods=['GET'], translate=True, **kwargs):
    def decorator(func):
        assert url.startswith('/')
        options = dict(kwargs, methods=methods)
        if translate:
            routes.append((
                url,
                func,
                dict(options, defaults={'lang_code': 'cs'}),
            ))
            routes.append((
Exemple #25
0
    def init_app(self, app):
        """Initialize this extension.

        if the config['JWT_OIDC_WELL_KNOWN_CONFIG'] is set, then try to load the JWKS_URI & ISSUER from that
        If it is not set
        attempt to load the JWKS_URI and ISSUE from the application config

        Required settings to function:
        WELL_KNOWN_CONFIG (optional) is this is set, the JWKS_URI & ISSUER will be loaded from there
        JWKS_URI: the endpoint defined for the jwks_keys
        ISSUER: the endpoint for the issuer of the tokens
        ALGORITHMS: only RS256 is supported
        AUDIENCE: the oidc audience (or API_IDENTIFIER)
        CLIENT_SECRET: the shared secret / key assigned to the client (audience)
        """
        self.app = app
        self.jwt_oidc_test_mode = app.config.get('JWT_OIDC_TEST_MODE', None)
        #
        # CHECK IF WE"RE RUNNING IN TEST_MODE!!
        #
        if self.jwt_oidc_test_mode:
            app.logger.debug(
                'JWT MANAGER running in test mode, using locally defined certs & tokens'
            )

            self.issuer = app.config.get('JWT_OIDC_TEST_ISSUER',
                                         'localhost.localdomain')
            self.jwt_oidc_test_keys = app.config.get('JWT_OIDC_TEST_KEYS',
                                                     None)
            self.audience = app.config.get('JWT_OIDC_TEST_AUDIENCE', None)
            self.client_secret = app.config.get('JWT_OIDC_TEST_CLIENT_SECRET',
                                                None)
            self.jwt_oidc_test_private_key_pem = app.config.get(
                'JWT_OIDC_TEST_PRIVATE_KEY_PEM', None)

            if self.jwt_oidc_test_keys:
                app.logger.debug('local key being used: {}'.format(
                    self.jwt_oidc_test_keys))
            else:
                app.logger.error(
                    'Attempting to run JWT Manager with no local key assigned')
                raise Exception(
                    'Attempting to run JWT Manager with no local key assigned')

        else:

            self.algorithms = app.config.get(
                'JWT_OIDC_ALGORITHMS', JwtManager.ALGORITHMS).replace(' ', '')\
                .split(',')

            # If the WELL_KNOWN_CONFIG is set, then go fetch the JWKS & ISSUER
            self.well_known_config = app.config.get(
                'JWT_OIDC_WELL_KNOWN_CONFIG', None)
            if self.well_known_config:
                # try to get the jwks & issuer from the well known config
                # jurl = urlopen(url=self.well_known_config, context=ssl.SSLContext()) # for gangster testing
                jurl = urlopen(url=self.well_known_config)
                self.well_known_obj_cache = json.loads(
                    jurl.read().decode('utf-8'))

                self.jwks_uri = self.well_known_obj_cache['jwks_uri']
                self.issuer = self.well_known_obj_cache['issuer']
            else:

                self.jwks_uri = app.config.get('JWT_OIDC_JWKS_URI', None)
                self.issuer = app.config.get('JWT_OIDC_ISSUER', None)

            # Setup JWKS caching
            self.caching_enabled = app.config.get('JWT_OIDC_CACHING_ENABLED',
                                                  False)
            if self.caching_enabled:
                self.cache = SimpleCache(default_timeout=app.config.get(
                    'JWT_OIDC_JWKS_CACHE_TIMEOUT', 300))

            self.audience = app.config.get('JWT_OIDC_AUDIENCE', None)
            self.client_secret = app.config.get('JWT_OIDC_CLIENT_SECRET', None)

        app.logger.debug('JWKS_URI: {}'.format(self.jwks_uri))
        app.logger.debug('ISSUER: {}'.format(self.issuer))
        app.logger.debug('ALGORITHMS: {}'.format(self.algorithms))
        app.logger.debug('AUDIENCE: {}'.format(self.audience))
        app.logger.debug('CLIENT_SECRET: {}'.format(self.client_secret))
        app.logger.debug('JWT_OIDC_TEST_MODE: {}'.format(
            self.jwt_oidc_test_mode))
        app.logger.debug('JWT_OIDC_TEST_KEYS: {}'.format(
            self.jwt_oidc_test_keys))

        # set the auth error handler
        auth_err_handler = app.config.get('JWT_OIDC_AUTH_ERROR_HANDLER',
                                          JwtManager.handle_auth_error)
        app.register_error_handler(AuthError, auth_err_handler)

        app.teardown_appcontext(self.teardown)
Exemple #26
0
def create_cache_list(request, tmpdir):
    mc = MemcachedCache()
    mc._client.flush_all()
    rc = RedisCache(port=6360)
    rc._client.flushdb()
    request.cls.cache_list = [FileSystemCache(tmpdir), mc, rc, SimpleCache()]
    UnsubscribeConfirmation = 'UnsubscribeConfirmation'


class InvalidMessageTypeException(Exception):
    pass


def verify_message_type(message_type: str):
    try:
        SNSMessageType(message_type)
    except ValueError:
        raise InvalidMessageTypeException(
            f'{message_type} is not a valid message type.')


certificate_cache = SimpleCache()


def get_certificate(url):
    res = certificate_cache.get(url)
    if res is not None:
        return res
    res = requests.get(url).content
    certificate_cache.set(url, res, timeout=60 * 60)  # 60 minutes
    return res


# 400 counts as a permanent failure so SNS will not retry.
# 500 counts as a failed delivery attempt so SNS will retry.
# See https://docs.aws.amazon.com/sns/latest/dg/DeliveryPolicies.html#DeliveryPolicies
@ses_callback_blueprint.route('/notifications/email/ses', methods=['POST'])
Exemple #28
0
from cachelib import SimpleCache
from werkzeug.urls import url_encode
from discodop import treebank
from discodop.tree import (Tree, DrawTree, DrawDependencies,
                           writediscbrackettree)
from discodop.parser import Parser, readparam, readgrammars, probstr
from discodop.util import tokenize

logging.basicConfig(format='%(asctime)s %(message)s',
                    datefmt='%Y-%m-%d %H:%M:%S',
                    level=logging.DEBUG)
APP = Flask(__name__)
LOG = APP.logger

LIMIT = 40  # maximum sentence length
CACHE = SimpleCache()
PARSERS = {}
SHOWFUNC = True  # show function tags in results
SHOWMORPH = True  # show morphological features in results
# POS tagged input is tokenized, and every token is of the form "word/POS"
# POS may be empty.
POSTAGS = re.compile(r'^\s*(?:\S+/\S*)(?:\s+\S+/\S*)*\s*$')


@APP.route('/')
def main():
    """Redirect to main page."""
    return redirect(url_for('index'))


@APP.route('/parser/')
Exemple #29
0
import os
import sys
import json
import time
import copy
import uuid
import threading

import requests
import docopt

from flask import Flask, render_template, request, send_from_directory, make_response
from cachelib import SimpleCache
from htmlmin.minify import html_minify

cache = SimpleCache()
app = Flask(__name__, static_url_path="/static")


def update_data():
    try:
        print("==> Updating team data ...")
        try:
            r = requests.get("https://slack.com/api/team.info",
                             params={"token": app.config["token"]})
            j = r.json()
            assert j["ok"]
        except:
            print("    !!! Failed to update team data.")
        else:
            team = j["team"]
COLOURSPACE_MODEL : unicode
    **{'CAM02LCD', 'CAM02SCD', 'CAM02UCS', 'CAM16LCD', 'CAM16SCD', 'CAM16UCS',
    'CIE XYZ', 'CIE xyY', 'CIE Lab', 'CIE Luv', 'CIE UCS', 'CIE UVW', 'DIN 99',
    'Hunter Lab', 'Hunter Rdab', 'ICTCP', 'IGPGTG', 'IPT', 'JzAzBz', 'OSA UCS',
    'hdr-CIELAB', 'hdr-IPT'}**
"""

DATA_POINTER_GAMUT = Lab_to_XYZ(LCHab_to_Lab(DATA_POINTER_GAMUT_VOLUME),
                                CCS_ILLUMINANT_POINTER_GAMUT)
"""
Pointer's Gamut data converted to *CIE XYZ* tristimulus values.

DATA_POINTER_GAMUT : ndarray
"""

IMAGE_CACHE = SimpleCache(default_timeout=60 * 24 * 7)
"""
Server side cache for images.

IMAGE_CACHE : SimpleCache
"""


def load_image(path, decoding_cctf='sRGB'):
    """
    Loads the image at given path and caches it in `IMAGE_CACHE` cache. If the
    image is already cached, it is returned directly.

    Parameters
    ----------
    path : unicode