Esempio n. 1
0
def _import_module_functions(module_path):
    '''Import a module and get the functions and return them in a dict'''
    module = importlib.import_module(module_path)
    return {
        k: v
        for k, v in authz.get_local_functions(module)
    }
Esempio n. 2
0
def _import_module_functions(
        module_path: str) -> dict[str, Callable[..., Any]]:
    '''Import a module and get the functions and return them in a dict'''
    module = importlib.import_module(module_path)
    return {
        k: v
        for k, v in authz.get_local_functions(module)
    }
Esempio n. 3
0
 def get_functions(module_root):
     import ckan.authz as authz
     fns = {}
     for auth_module_name in [
             "get",
             "create",
             "update",
             "delete",
             "patch",
     ]:
         module_path = "%s.%s" % (module_root, auth_module_name)
         module = importlib.import_module(module_path)
         members = authz.get_local_functions(module)
         for key, v in members:
             name = "%s: %s" % (auth_module_name, key)
             fns[name] = v
     return fns
Esempio n. 4
0
def _get_public_members(module: types.ModuleType) -> dict[str, Any]:
    return _get_explicit_members(module) or dict(get_local_functions(module))
Esempio n. 5
0
def get_action(action: str) -> Action:
    '''Return the named :py:mod:`ckan.logic.action` function.

    For example ``get_action('package_create')`` will normally return the
    :py:func:`ckan.logic.action.create.package_create()` function.

    For documentation of the available action functions, see
    :ref:`api-reference`.

    You should always use ``get_action()`` instead of importing an action
    function directly, because :py:class:`~ckan.plugins.interfaces.IActions`
    plugins can override action functions, causing ``get_action()`` to return a
    plugin-provided function instead of the default one.

    Usage::

        import ckan.plugins.toolkit as toolkit

        # Call the package_create action function:
        toolkit.get_action('package_create')(context, data_dict)

    As the context parameter passed to an action function is commonly::

        context = {'model': ckan.model, 'session': ckan.model.Session,
                   'user': user}

    an action function returned by ``get_action()`` will automatically add
    these parameters to the context if they are not defined.  This is
    especially useful for plugins as they should not really be importing parts
    of ckan eg :py:mod:`ckan.model` and as such do not have access to ``model``
    or ``model.Session``.

    If a ``context`` of ``None`` is passed to the action function then the
    default context dict will be created.

    .. note::

        Many action functions modify the context dict. It can therefore
        not be reused for multiple calls of the same or different action
        functions.

    :param action: name of the action function to return,
        eg. ``'package_create'``
    :type action: string

    :returns: the named action function
    :rtype: callable

    '''

    if _actions:
        if action not in _actions:
            raise KeyError("Action '%s' not found" % action)
        return _actions[action]
    # Otherwise look in all the plugins to resolve all possible First
    # get the default ones in the ckan/logic/action directory Rather
    # than writing them out in full will use importlib.import_module
    # to load anything from ckan.logic.action that looks like it might
    # be an action
    for action_module_name in ['get', 'create', 'update', 'delete', 'patch']:
        module = importlib.import_module(
            '.' + action_module_name, 'ckan.logic.action')
        for k, v in authz.get_local_functions(module):
            _actions[k] = v
            # Whitelist all actions defined in logic/action/get.py as
            # being side-effect free.
            if action_module_name == 'get' and \
               not hasattr(v, 'side_effect_free'):
                v.side_effect_free = True

    # Then overwrite them with any specific ones in the plugins:
    resolved_action_plugins: dict[str, str] = {}
    fetched_actions = {}
    chained_actions = defaultdict(list)
    for plugin in p.PluginImplementations(p.IActions):
        for name, action_function in plugin.get_actions().items():
            if _is_chained_action(action_function):
                chained_actions[name].append(action_function)
            elif name in resolved_action_plugins:
                raise NameConflict(
                    'The action %r is already implemented in %r' % (
                        name,
                        resolved_action_plugins[name]
                    )
                )
            else:
                resolved_action_plugins[name] = plugin.name
                # Extensions are exempted from the auth audit for now
                # This needs to be resolved later
                # type_ignore_reason: custom attribute
                action_function.auth_audit_exempt = True  # type: ignore
                fetched_actions[name] = action_function
    for name, func_list in chained_actions.items():
        if name not in fetched_actions and name not in _actions:
            # nothing to override from plugins or core
            raise NotFound('The action %r is not found for chained action' % (
                name))
        for func in reversed(func_list):
            # try other plugins first, fall back to core
            prev_func = fetched_actions.get(name, _actions.get(name))
            new_func = functools.partial(func, prev_func)
            # persisting attributes to the new partial function
            for attribute, value in func.__dict__.items():
                setattr(new_func, attribute, value)
            fetched_actions[name] = new_func

    # Use the updated ones in preference to the originals.
    _actions.update(fetched_actions)

    # wrap the functions
    for action_name, _action in _actions.items():
        def make_wrapped(_action: Action, action_name: str):
            def wrapped(context: Optional[Context],
                        data_dict: DataDict, **kw: Any):
                if kw:
                    log.critical('%s was passed extra keywords %r'
                                 % (_action.__name__, kw))

                context = _prepopulate_context(context)

                # Auth Auditing - checks that the action function did call
                # check_access (unless there is no accompanying auth function).
                # We push the action name and id onto the __auth_audit stack
                # before calling the action, and check_access removes it.
                # (We need the id of the action in case the action is wrapped
                # inside an action of the same name, which happens in the
                # datastore)
                context.setdefault('__auth_audit', [])
                context['__auth_audit'].append((action_name, id(_action)))

                # check_access(action_name, context, data_dict=None)
                result = _action(context, data_dict, **kw)
                try:
                    audit = context['__auth_audit'][-1]
                    if audit[0] == action_name and audit[1] == id(_action):
                        if action_name not in authz.auth_functions_list():
                            log.debug('No auth function for %s' % action_name)
                        elif not getattr(_action, 'auth_audit_exempt', False):
                            raise Exception(
                                'Action function {0} did not call its '
                                'auth function'
                                .format(action_name))
                        # remove from audit stack
                        context['__auth_audit'].pop()
                except IndexError:
                    pass

                return result
            return wrapped

        fn = make_wrapped(_action, action_name)
        # we need to mirror the docstring
        fn.__doc__ = _action.__doc__
        # we need to retain the side effect free behaviour
        if getattr(_action, 'side_effect_free', False):
            # type_ignore_reason: custom attribute
            fn.side_effect_free = True  # type: ignore
        _actions[action_name] = fn

    return _actions[action]