Example #1
0
def check_access(action, context, data_dict=None):
    action = new_authz.clean_action_name(action)

    # Auth Auditing.  We remove this call from the __auth_audit stack to show
    # we have called the auth function
    try:
        audit = context.get('__auth_audit', [])[-1]
    except IndexError:
        audit = ''
    if audit and audit[0] == action:
        context['__auth_audit'].pop()

    user = context.get('user')
    log.debug('check access - user %r, action %s' % (user, action))

    if action:
        #if action != model.Action.READ and user in
        # (model.PSEUDO_USER__VISITOR, ''):
        #    # TODO Check the API key is valid at some point too!
        #    log.debug('Valid API key needed to make changes')
        #    raise NotAuthorized
        logic_authorization = new_authz.is_authorized(action, context, data_dict)
        if not logic_authorization['success']:
            msg = logic_authorization.get('msg', '')
            raise NotAuthorized(msg)
    elif not user:
        msg = _('No valid API key provided.')
        log.debug(msg)
        raise NotAuthorized(msg)

    log.debug('Access OK.')
    return True
Example #2
0
def check_access(action, context, data_dict=None):
    action = new_authz.clean_action_name(action)

    # Auth Auditing.  We remove this call from the __auth_audit stack to show
    # we have called the auth function
    try:
        audit = context.get('__auth_audit', [])[-1]
    except IndexError:
        audit = ''
    if audit and audit[0] == action:
        context['__auth_audit'].pop()

    user = context.get('user')
    log.debug('check access - user %r, action %s' % (user, action))

    if action:
        #if action != model.Action.READ and user in
        # (model.PSEUDO_USER__VISITOR, ''):
        #    # TODO Check the API key is valid at some point too!
        #    log.debug('Valid API key needed to make changes')
        #    raise NotAuthorized
        logic_authorization = new_authz.is_authorized(action, context,
                                                      data_dict)
        if not logic_authorization['success']:
            msg = logic_authorization.get('msg', '')
            raise NotAuthorized(msg)
    elif not user:
        msg = _('No valid API key provided.')
        log.debug(msg)
        raise NotAuthorized(msg)

    log.debug('Access OK.')
    return True
Example #3
0
def extract_actions():
    actions = {}

    for action_module_name in ['get', 'create', 'update', 'delete']:
        log.info('Fetching actions from '
                 'ckan.logic.actions.{0}.py'.format(action_module_name))
        count = 0
        actions[action_module_name] = {}
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if check_function(k):
                # Only load functions from the action module or already
                # replaced functions.
                if (hasattr(v, '__call__') and
                    (v.__module__ == module_path or hasattr(v, '__replaced'))):
                    k = new_authz.clean_action_name(k)
                    actions[action_module_name][k] = get_doc(k)
    return actions
Example #4
0
def extract_actions():
    actions = {}

    for action_module_name in ['get', 'create', 'update', 'delete']:
        log.info('Fetching actions from '
                 'ckan.logic.actions.{0}.py'.format(action_module_name))
        count = 0
        actions[action_module_name] = {}
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if check_function(k):
                # Only load functions from the action module or already
                # replaced functions.
                if (hasattr(v, '__call__')
                        and (v.__module__ == module_path
                             or hasattr(v, '__replaced'))):
                    k = new_authz.clean_action_name(k)
                    actions[action_module_name][k] = get_doc(k)
    return actions
Example #5
0
def get_action(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': pylons.c.user or pylons.c.author}

    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.

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

    :returns: the named action function
    :rtype: callable

    '''
    # clean the action names
    action = new_authz.clean_action_name(action)

    if _actions:
        if not action in _actions:
            raise KeyError("Action '%s' not found" % action)
        return _actions.get(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 __import__
    # to load anything from ckan.logic.action that looks like it might
    # be an action
    for action_module_name in ['get', 'create', 'update', 'delete']:
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if not k.startswith('_'):
                # Only load functions from the action module or already
                # replaced functions.
                if (hasattr(v, '__call__')
                        and (v.__module__ == module_path
                             or hasattr(v, '__replaced'))):
                    k = new_authz.clean_action_name(k)
                    _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 = {}
    fetched_actions = {}
    for plugin in p.PluginImplementations(p.IActions):
        for name, auth_function in plugin.get_actions().items():
            name = new_authz.clean_action_name(name)
            if name in resolved_action_plugins:
                raise Exception(
                    'The action %r is already implemented in %r' % (
                        name,
                        resolved_action_plugins[name]
                    )
                )
            log.debug('Action function {0} from plugin {1} was inserted'.format(name, plugin.name))
            resolved_action_plugins[name] = plugin.name
            # Extensions are exempted from the auth audit for now
            # This needs to be resolved later
            auth_function.auth_audit_exempt = True
            fetched_actions[name] = auth_function
    # 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_name):
            def wrapped(context=None, data_dict=None, **kw):
                if kw:
                    log.critical('%s was passed extra keywords %r'
                                 % (_action.__name__, kw))

                context = _prepopulate_context(context)

                # Auth Auditing
                # store this action name in the auth audit so we can see if
                # check access was called on the function we store the id of
                # the action incase the action is wrapped inside an action
                # of the same name.  this 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 new_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

        # If we have been called multiple times for example during tests then
        # we need to make sure that we do not rewrap the actions.
        if hasattr(_action, '__replaced'):
            _actions[action_name] = _action.__replaced
            continue

        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):
            fn.side_effect_free = True
        _actions[action_name] = fn

    return _actions.get(action)
Example #6
0
def check_access(action, context, data_dict=None):
    '''Calls the authorization function for the provided action

    This is the only function that should be called to determine whether a
    user (or an anonymous request) is allowed to perform a particular action.

    The function accepts a context object, which should contain a 'user' key
    with the name of the user performing the action, and optionally a
    dictionary with extra data to be passed to the authorization function.

    For example::

        check_access('package_update', context, data_dict)

    If not already there, the function will add an `auth_user_obj` key to the
    context object with the actual User object (in case it exists in the
    database). This check is only performed once per context object.

    Raise :py:exc:`~ckan.plugins.toolkit.NotAuthorized` if the user is not
    authorized to call the named action function.

    If the user *is* authorized to call the action, return ``True``.

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

    :param context:
    :type context: dict

    :param data_dict:
    :type data_dict: dict

    :raises: :py:exc:`~ckan.plugins.toolkit.NotAuthorized` if the user is not
        authorized to call the named action

    '''
    action = new_authz.clean_action_name(action)

    # Auth Auditing.  We remove this call from the __auth_audit stack to show
    # we have called the auth function
    try:
        audit = context.get('__auth_audit', [])[-1]
    except IndexError:
        audit = ''
    if audit and audit[0] == action:
        context['__auth_audit'].pop()

    user = context.get('user')
    log.debug('check access - user %r, action %s' % (user, action))

    if not 'auth_user_obj' in context:
        context['auth_user_obj'] = None

    if not context.get('ignore_auth'):
        if not context.get('__auth_user_obj_checked'):
            if context.get('user') and not context.get('auth_user_obj'):
                context['auth_user_obj'] = model.User.by_name(context['user'])
            context['__auth_user_obj_checked'] = True

    context = _prepopulate_context(context)

    logic_authorization = new_authz.is_authorized(action, context, data_dict)
    if not logic_authorization['success']:
        msg = logic_authorization.get('msg', '')
        raise NotAuthorized(msg)

    log.debug('Access OK.')
    return True
Example #7
0
def get_action(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': pylons.c.user or pylons.c.author}

    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.

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

    :returns: the named action function
    :rtype: callable

    '''
    # clean the action names
    action = new_authz.clean_action_name(action)

    if _actions:
        if not action in _actions:
            raise KeyError("Action '%s' not found" % action)
        return _actions.get(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 __import__
    # to load anything from ckan.logic.action that looks like it might
    # be an action
    for action_module_name in ['get', 'create', 'update', 'delete']:
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if not k.startswith('_'):
                # Only load functions from the action module or already
                # replaced functions.
                if (hasattr(v, '__call__') and
                    (v.__module__ == module_path or hasattr(v, '__replaced'))):
                    k = new_authz.clean_action_name(k)
                    _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 = {}
    fetched_actions = {}
    for plugin in p.PluginImplementations(p.IActions):
        for name, auth_function in plugin.get_actions().items():
            name = new_authz.clean_action_name(name)
            if name in resolved_action_plugins:
                raise Exception('The action %r is already implemented in %r' %
                                (name, resolved_action_plugins[name]))
            log.debug(
                'Action function {0} from plugin {1} was inserted'.format(
                    name, plugin.name))
            resolved_action_plugins[name] = plugin.name
            # Extensions are exempted from the auth audit for now
            # This needs to be resolved later
            auth_function.auth_audit_exempt = True
            fetched_actions[name] = auth_function
    # 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_name):
            def wrapped(context=None, data_dict=None, **kw):
                if kw:
                    log.critical('%s was passed extra keywords %r' %
                                 (_action.__name__, kw))

                context = _prepopulate_context(context)

                # Auth Auditing
                # store this action name in the auth audit so we can see if
                # check access was called on the function we store the id of
                # the action incase the action is wrapped inside an action
                # of the same name.  this 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 new_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

        # If we have been called multiple times for example during tests then
        # we need to make sure that we do not rewrap the actions.
        if hasattr(_action, '__replaced'):
            _actions[action_name] = _action.__replaced
            continue

        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):
            fn.side_effect_free = True
        _actions[action_name] = fn

        def replaced_action(action_name):
            def warn(context, data_dict):
                log.critical('Action `%s` is being called directly '
                             'all action calls should be accessed via '
                             'logic.get_action' % action_name)
                return get_action(action_name)(context, data_dict)

            return warn

        # Store our wrapped function so it is available.  This is to prevent
        # rewrapping of actions
        module = sys.modules[_action.__module__]
        r = replaced_action(action_name)
        r.__replaced = fn
        module.__dict__[action_name] = r

    return _actions.get(action)
Example #8
0
def check_access(action, context, data_dict=None):
    '''Calls the authorization function for the provided action

    This is the only function that should be called to determine whether a
    user (or an anonymous request) is allowed to perform a particular action.

    The function accepts a context object, which should contain a 'user' key
    with the name of the user performing the action, and optionally a
    dictionary with extra data to be passed to the authorization function.

    For example::

        check_access('package_update', context, data_dict)

    If not already there, the function will add an `auth_user_obj` key to the
    context object with the actual User object (in case it exists in the
    database). This check is only performed once per context object.

    Raise :py:exc:`~ckan.plugins.toolkit.NotAuthorized` if the user is not
    authorized to call the named action function.

    If the user *is* authorized to call the action, return ``True``.

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

    :param context:
    :type context: dict

    :param data_dict:
    :type data_dict: dict

    :raises: :py:exc:`~ckan.plugins.toolkit.NotAuthorized` if the user is not
        authorized to call the named action

    '''
    action = new_authz.clean_action_name(action)

    # Auth Auditing.  We remove this call from the __auth_audit stack to show
    # we have called the auth function
    try:
        audit = context.get('__auth_audit', [])[-1]
    except IndexError:
        audit = ''
    if audit and audit[0] == action:
        context['__auth_audit'].pop()

    user = context.get('user')
    log.debug('check access - user %r, action %s' % (user, action))

    if not 'auth_user_obj' in context:
        context['auth_user_obj'] = None

    if not context.get('ignore_auth'):
        if not context.get('__auth_user_obj_checked'):
            if context.get('user') and not context.get('auth_user_obj'):
                context['auth_user_obj'] = model.User.by_name(context['user'])
            context['__auth_user_obj_checked'] = True

    context = _prepopulate_context(context)

    logic_authorization = new_authz.is_authorized(action, context, data_dict)
    if not logic_authorization['success']:
        msg = logic_authorization.get('msg', '')
        raise NotAuthorized(msg)

    log.debug('Access OK.')
    return True
Example #9
0
def get_action(action):
    '''Return the ckan.logic.action function named by the given string.

    For example:

        get_action('package_create')

    will normally return the ckan.logic.action.create.py:package_create()
    function.

    Rather than importing a ckan.logic.action function and calling it directly,
    you should always fetch the function via get_action():

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

    This is because CKAN plugins can override action functions using the
    IActions plugin interface, causing get_action() to return a plugin-provided
    function instead of the default one.

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

        context = {'model': ckan.model, 'session': ckan.model.Session,
                   'user': pylons.c.user or pylons.c.author}

    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 extensions as they should not really be importing parts of ckan
    eg 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 context dict
    will be created.

    :param action: name of the action function to return
    :type action: string

    :returns: the named action function
    :rtype: callable

    '''

    # clean the action names
    action = new_authz.clean_action_name(action)

    if _actions:
        if not action in _actions:
            raise KeyError("Action '%s' not found" % action)
        return _actions.get(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 __import__
    # to load anything from ckan.logic.action that looks like it might
    # be an action
    for action_module_name in ['get', 'create', 'update', 'delete']:
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if not k.startswith('_'):
                # Only load functions from the action module.
                if isinstance(v, types.FunctionType):
                    k = new_authz.clean_action_name(k)
                    _actions[k] = v

                    # Whitelist all actions defined in logic/action/get.py as
                    # being side-effect free.
                    v.side_effect_free = getattr(v, 'side_effect_free', True)\
                        and action_module_name == 'get'

    # Then overwrite them with any specific ones in the plugins:
    resolved_action_plugins = {}
    fetched_actions = {}
    for plugin in p.PluginImplementations(p.IActions):
        for name, auth_function in plugin.get_actions().items():
            name = new_authz.clean_action_name(name)
            if name in resolved_action_plugins:
                raise Exception(
                    'The action %r is already implemented in %r' % (
                        name,
                        resolved_action_plugins[name]
                    )
                )
            log.debug('Auth function %r was inserted', plugin.name)
            resolved_action_plugins[name] = plugin.name
            fetched_actions[name] = auth_function
    # 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_name):
            def wrapped(context=None, data_dict=None, **kw):
                if kw:
                    log.critical('%s was pass extra keywords %r'
                                 % (_action.__name__, kw))
                if context is None:
                    context = {}
                context.setdefault('model', model)
                context.setdefault('session', model.Session)
                try:
                    context.setdefault('user', c.user or c.author)
                except TypeError:
                    # c not registered
                    pass
                return _action(context, data_dict, **kw)
            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):
            fn.side_effect_free = True
        _actions[action_name] = fn

    return _actions.get(action)
Example #10
0
def get_action(action):
    '''Return the ckan.logic.action function named by the given string.

    For example:

        get_action('package_create')

    will normally return the ckan.logic.action.create.py:package_create()
    function.

    Rather than importing a ckan.logic.action function and calling it directly,
    you should always fetch the function via get_action():

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

    This is because CKAN plugins can override action functions using the
    IActions plugin interface, causing get_action() to return a plugin-provided
    function instead of the default one.

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

        context = {'model': ckan.model, 'session': ckan.model.Session,
                   'user': pylons.c.user or pylons.c.author}

    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 extensions as they should not really be importing parts of ckan
    eg 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 context dict
    will be created.

    :param action: name of the action function to return
    :type action: string

    :returns: the named action function
    :rtype: callable

    '''

    # clean the action names
    action = new_authz.clean_action_name(action)

    if _actions:
        if not action in _actions:
            raise KeyError("Action '%s' not found" % action)
        return _actions.get(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 __import__
    # to load anything from ckan.logic.action that looks like it might
    # be an action
    for action_module_name in ['get', 'create', 'update', 'delete']:
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if not k.startswith('_'):
                # Only load functions from the action module.
                if isinstance(v, types.FunctionType):
                    k = new_authz.clean_action_name(k)
                    _actions[k] = v

                    # Whitelist all actions defined in logic/action/get.py as
                    # being side-effect free.
                    v.side_effect_free = getattr(v, 'side_effect_free', True)\
                        and action_module_name == 'get'

    # Then overwrite them with any specific ones in the plugins:
    resolved_action_plugins = {}
    fetched_actions = {}
    for plugin in p.PluginImplementations(p.IActions):
        for name, auth_function in plugin.get_actions().items():
            name = new_authz.clean_action_name(name)
            if name in resolved_action_plugins:
                raise Exception('The action %r is already implemented in %r' %
                                (name, resolved_action_plugins[name]))
            log.debug('Auth function %r was inserted', plugin.name)
            resolved_action_plugins[name] = plugin.name
            fetched_actions[name] = auth_function
    # 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_name):
            def wrapped(context=None, data_dict=None, **kw):
                if kw:
                    log.critical('%s was pass extra keywords %r' %
                                 (_action.__name__, kw))
                if context is None:
                    context = {}
                context.setdefault('model', model)
                context.setdefault('session', model.Session)
                try:
                    context.setdefault('user', c.user or c.author)
                except TypeError:
                    # c not registered
                    pass
                return _action(context, data_dict, **kw)

            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):
            fn.side_effect_free = True
        _actions[action_name] = fn

    return _actions.get(action)
Example #11
0
def get_action(action):
    '''Return the ckan.logic.action function named by the given string.

    For example:

        get_action('package_create')

    will normally return the ckan.logic.action.create.py:package_create()
    function.

    Rather than importing a ckan.logic.action function and calling it directly,
    you should always fetch the function via get_action():

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

    This is because CKAN plugins can override action functions using the
    IActions plugin interface, causing get_action() to return a plugin-provided
    function instead of the default one.

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

        context = {'model': ckan.model, 'session': ckan.model.Session,
                   'user': pylons.c.user or pylons.c.author}

    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 extensions as they should not really be importing parts of ckan
    eg 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 context dict
    will be created.

    :param action: name of the action function to return
    :type action: string

    :returns: the named action function
    :rtype: callable

    '''

    # clean the action names
    action = new_authz.clean_action_name(action)

    if _actions:
        if not action in _actions:
            raise KeyError("Action '%s' not found" % action)
        return _actions.get(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 __import__
    # to load anything from ckan.logic.action that looks like it might
    # be an action
    for action_module_name in ['get', 'create', 'update', 'delete']:
        module_path = 'ckan.logic.action.' + action_module_name
        module = __import__(module_path)
        for part in module_path.split('.')[1:]:
            module = getattr(module, part)
        for k, v in module.__dict__.items():
            if not k.startswith('_'):
                # Only load functions from the action module or already
                # replaced functions.
                if (hasattr(v, '__call__')
                        and (v.__module__ == module_path
                             or hasattr(v, '__replaced'))):
                    k = new_authz.clean_action_name(k)
                    _actions[k] = v

                    # Whitelist all actions defined in logic/action/get.py as
                    # being side-effect free.
                    # FIXME This looks wrong should it be an 'or' not 'and'
                    v.side_effect_free = getattr(v, 'side_effect_free', True)\
                        and action_module_name == 'get'

    # Then overwrite them with any specific ones in the plugins:
    resolved_action_plugins = {}
    fetched_actions = {}
    for plugin in p.PluginImplementations(p.IActions):
        for name, auth_function in plugin.get_actions().items():
            name = new_authz.clean_action_name(name)
            if name in resolved_action_plugins:
                raise Exception(
                    'The action %r is already implemented in %r' % (
                        name,
                        resolved_action_plugins[name]
                    )
                )
            log.debug('Auth function %r was inserted', plugin.name)
            resolved_action_plugins[name] = plugin.name
            # Extensions are exempted from the auth audit for now
            # This needs to be resolved later
            auth_function.auth_audit_exempt = True
            fetched_actions[name] = auth_function
    # 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_name):
            def wrapped(context=None, data_dict=None, **kw):
                if kw:
                    log.critical('%s was pass extra keywords %r'
                                 % (_action.__name__, kw))
                if context is None:
                    context = {}
                context.setdefault('model', model)
                context.setdefault('session', model.Session)
                try:
                    context.setdefault('user', c.user or c.author)
                except TypeError:
                    # c not registered
                    pass

                # Auth Auditing
                # store this action name in the auth audit so we can see if
                # check access was called on the function we store the id of
                # the action incase the action is wrapped inside an action
                # of the same name.  this 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 new_authz.auth_functions_list():
                            log.debug('No auth function for %s' % action_name)
                        elif not getattr(_action, 'auth_audit_exempt', False):
                            raise Exception('Action Auth Audit: %s' % action_name)
                        # remove from audit stack
                        context['__auth_audit'].pop()
                except IndexError:
                    pass
                return result
            return wrapped

        # If we have been called multiple times for example during tests then
        # we need to make sure that we do not rewrap the actions.
        if hasattr(_action, '__replaced'):
            _actions[action_name] = _action.__replaced
            continue

        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):
            fn.side_effect_free = True
        _actions[action_name] = fn


        def replaced_action(action_name):
            def warn(context, data_dict):
                log.critical('Action `%s` is being called directly '
                             'all action calls should be accessed via '
                             'logic.get_action' % action_name)
                return get_action(action_name)(context, data_dict)
            return warn

        # Store our wrapped function so it is available.  This is to prevent
        # rewrapping of actions
        module = sys.modules[_action.__module__]
        r = replaced_action(action_name)
        r.__replaced = fn
        module.__dict__[action_name] = r

    return _actions.get(action)