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
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
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)
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
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)
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)
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)
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)