def task_status_update_many( context: Context, data_dict: DataDict) -> ActionResult.TaskStatusUpdateMany: '''Update many task statuses at once. :param data: the task_status dictionaries to update, for the format of task status dictionaries see :py:func:`~task_status_update` :type data: list of dictionaries :returns: the updated task statuses :rtype: list of dictionaries ''' results = [] model = context['model'] deferred = context.get('defer_commit') context['defer_commit'] = True for data in data_dict['data']: results.append(_get_action('task_status_update')(context, data)) if not deferred: context.pop('defer_commit') if not context.get('defer_commit'): model.Session.commit() return {'results': results}
def ignore_not_package_admin(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: '''Ignore if the user is not allowed to administer the package specified.''' user = context.get('user') if 'ignore_auth' in context: return if user and authz.is_sysadmin(user): return authorized = False pkg = context.get('package') if pkg: try: logic.check_access('package_change_state', context) authorized = True except logic.NotAuthorized: authorized = False if (user and pkg and authorized): return # allow_state_change in the context will allow the state to be changed # FIXME is this the best way to cjeck for state only? if key == ('state', ) and context.get('allow_state_change'): return data.pop(key)
def tag_list_dictize(tag_list: Union[Iterable[model.Tag], Iterable[tuple[model.Tag, str]]], context: Context) -> list[dict[str, Any]]: result_list: list[dict[str, Any]] = [] for tag in tag_list: if context.get('with_capacity'): assert not isinstance(tag, model.Tag) tag, capacity = tag dictized = d.table_dictize(tag, context, capacity=capacity) else: dictized = d.table_dictize(tag, context) # Add display_names to tag dicts. At first a tag's display_name is just # the same as its name, but the display_name might get changed later # (e.g. translated into another language by the multilingual # extension). assert 'display_name' not in dictized dictized['display_name'] = dictized['name'] if context.get('for_view'): for item in plugins.PluginImplementations(plugins.ITagController): dictized = item.before_view(dictized) result_list.append(dictized) return result_list
def owner_org_validator(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: value = data.get(key) if value is missing or value is None: if not authz.check_config_permission('create_unowned_dataset'): raise Invalid(_('An organization must be provided')) data.pop(key, None) raise df.StopOnError model = context['model'] user = model.User.get(context['user']) package = context.get('package') if value == '': if not authz.check_config_permission('create_unowned_dataset'): raise Invalid(_('An organization must be provided')) return if (authz.check_config_permission('allow_dataset_collaborators') and not authz.check_config_permission( 'allow_collaborators_to_change_owner_org')): if package and user and not user.sysadmin: is_collaborator = authz.user_is_collaborator_on_dataset( user.id, package.id, ['admin', 'editor']) if is_collaborator: # User is a collaborator, check if it's also a member with # edit rights of the current organization (redundant, but possible) user_orgs = logic.get_action('organization_list_for_user')( { 'ignore_auth': True }, { 'id': user.id, 'permission': 'update_dataset' }) user_is_org_member = package.owner_org in [ org['id'] for org in user_orgs ] if data.get( key) != package.owner_org and not user_is_org_member: raise Invalid( _('You cannot move this dataset to another organization' )) group = model.Group.get(value) if not group: raise Invalid(_('Organization does not exist')) group_id = group.id if not package or (package and package.owner_org != group_id): # This is a new dataset or we are changing the organization if not context.get(u'ignore_auth', False) and (not user or not ( user.sysadmin or authz.has_user_permission_for_group_or_org( group_id, user.name, 'create_dataset'))): raise Invalid(_('You cannot add a dataset to this organization')) data[key] = group_id
def wrapper(context: Context, data_dict: DataDict): if can_skip_validator: if context.get('skip_validation'): return action(context, data_dict) schema = context.get('schema', schema_func()) data_dict, errors = _validate(data_dict, schema, context) if errors: raise ValidationError(errors) return action(context, data_dict)
def ignore_not_sysadmin(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: '''Ignore the field if user not sysadmin or ignore_auth in context.''' user = context.get('user') ignore_auth = context.get('ignore_auth') if ignore_auth or (user and authz.is_sysadmin(user)): return data.pop(key)
def empty_if_not_sysadmin(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: '''Only sysadmins may pass this value''' from ckan.lib.navl.validators import empty user = context.get('user') ignore_auth = context.get('ignore_auth') if ignore_auth or (user and authz.is_sysadmin(user)): return empty(key, data, errors, context)
def resource_view_update( context: Context, data_dict: DataDict) -> ActionResult.ResourceViewUpdate: '''Update a resource view. To update a resource_view you must be authorized to update the resource that the resource_view belongs to. For further parameters see ``resource_view_create()``. :param id: the id of the resource_view to update :type id: string :returns: the updated resource_view :rtype: string ''' model = context['model'] id = _get_or_bust(data_dict, "id") resource_view = model.ResourceView.get(id) if not resource_view: raise NotFound view_plugin = ckan.lib.datapreview.get_view_plugin(resource_view.view_type) schema = (context.get('schema') or schema_.default_update_resource_view_schema(view_plugin)) assert view_plugin plugin_schema = view_plugin.info().get('schema', {}) schema.update(plugin_schema) data, errors = _validate(data_dict, schema, context) if errors: model.Session.rollback() raise ValidationError(errors) context['resource_view'] = resource_view resource = model.Resource.get(resource_view.resource_id) if resource is None: raise NotFound('Resource was not found.') context['resource'] = resource _check_access('resource_view_update', context, data_dict) if context.get('preview'): return data resource_view = model_save.resource_view_dict_save(data, context) if not context.get('defer_commit'): model.repo.commit() return model_dictize.resource_view_dictize(resource_view, context)
def _update_package_relationship( relationship: 'model_.PackageRelationship', comment: str, context: Context) -> dict[str, Any]: model = context['model'] api = context.get('api_version') ref_package_by = 'id' if api == 2 else 'name' is_changed = relationship.comment != comment if is_changed: relationship.comment = comment if not context.get('defer_commit'): model.repo.commit_and_remove() rel_dict = relationship.as_dict(package=relationship.subject, ref_package_by=ref_package_by) return rel_dict
def activity_create(context: Context, data_dict: DataDict) -> Optional[dict[str, Any]]: """Create a new activity stream activity. You must be a sysadmin to create new activities. :param user_id: the name or id of the user who carried out the activity, e.g. ``'seanh'`` :type user_id: string :param object_id: the name or id of the object of the activity, e.g. ``'my_dataset'`` :param activity_type: the type of the activity, this must be an activity type that CKAN knows how to render, e.g. ``'new package'``, ``'changed user'``, ``'deleted group'`` etc. :type activity_type: string :param data: any additional data about the activity :type data: dictionary :returns: the newly created activity :rtype: dictionary """ tk.check_access("activity_create", context, data_dict) if not tk.config.get_value("ckan.activity_streams_enabled"): return model = context["model"] # Any revision_id that the caller attempts to pass in the activity_dict is # ignored and removed here. if "revision_id" in data_dict: del data_dict["revision_id"] sch = context.get("schema") or schema.default_create_activity_schema() data, errors = tk.navl_validate(data_dict, sch, context) if errors: raise tk.ValidationError(errors) activity = activity_dict_save(data, context) if not context.get("defer_commit"): model.repo.commit() log.debug("Created '%s' activity" % activity.activity_type) return model_activity.activity_dictize(activity, context)
def resource_show(context: Context, data_dict: DataDict) -> AuthResult: model = context['model'] user = context.get('user') resource = get_resource_object(context, data_dict) # check authentication against package assert resource.package_id pkg = model.Package.get(resource.package_id) if not pkg: raise logic.NotFound( _('No package found for this resource, cannot check auth.')) pkg_dict = {'id': pkg.id} authorized = authz.is_authorized('package_show', context, pkg_dict).get('success') if not authorized: return { 'success': False, 'msg': _('User %s not authorized to read resource %s') % (user, resource.id) } else: return {'success': True}
def resource_create(context: Context, data_dict: DataDict) -> AuthResult: model = context['model'] user = context.get('user') package_id = data_dict.get('package_id') if not package_id and data_dict.get('id'): # This can happen when auth is deferred, eg from `resource_view_create` resource = logic_auth.get_resource_object(context, data_dict) package_id = resource.package_id if not package_id: raise logic.NotFound(_('No dataset id provided, cannot check auth.')) # check authentication against package pkg = model.Package.get(package_id) if not pkg: raise logic.NotFound( _('No package found for this resource, cannot check auth.')) pkg_dict = {'id': pkg.id} authorized = authz.is_authorized('package_update', context, pkg_dict).get('success') if not authorized: return { 'success': False, 'msg': _('User %s not authorized to create resources on dataset %s') % (str(user), package_id) } else: return {'success': True}
def package_extras_save(extra_dicts: Optional[list[dict[str, Any]]], pkg: 'model.Package', context: Context) -> None: allow_partial_update = context.get("allow_partial_update", False) if extra_dicts is None and allow_partial_update: return session = context["session"] old_extras = pkg._extras new_extras: dict[str, Any] = {} for extra_dict in extra_dicts or []: if extra_dict.get("deleted"): continue if extra_dict['value'] is None: pass else: new_extras[extra_dict["key"]] = extra_dict["value"] #new for key in set(new_extras.keys()) - set(old_extras.keys()): pkg.extras[key] = new_extras[key] #changed for key in set(new_extras.keys()) & set(old_extras.keys()): extra = old_extras[key] if new_extras[key] == extra.value: continue extra.value = new_extras[key] session.add(extra) #deleted for key in set(old_extras.keys()) - set(new_extras.keys()): extra = old_extras[key] session.delete(extra)
def package_resource_list_save(res_dicts: Optional[list[dict[str, Any]]], package: 'model.Package', context: Context) -> None: allow_partial_update = context.get("allow_partial_update", False) if res_dicts is None and allow_partial_update: return resource_list = package.resources_all old_list = package.resources_all[:] obj_list = [] for res_dict in res_dicts or []: if not u'package_id' in res_dict or not res_dict[u'package_id']: res_dict[u'package_id'] = package.id obj = resource_dict_save(res_dict, context) obj_list.append(obj) # Set the package's resources. resource_list is an ORM relation - the # package's resources. If we didn't have the slice operator "[:]" then it # would reassign the variable "resource_list" to be the obj_list. But with # the slice operator it changes the contents of the relation, setting the # package's resources. # At the table level, for each resource in the obj_list, its # resource.package_id is changed to this package (which is needed for new # resources), and every resource.position is set to ascending integers, # according to their ordering in the obj_list. resource_list[:] = obj_list # Mark any left-over resources as deleted for resource in set(old_list) - set(obj_list): resource.state = 'deleted' resource_list.append(resource)
def tag_dict_save(tag_dict: dict[str, Any], context: Context) -> 'model.Tag': model = context['model'] tag = context.get('tag') if tag: tag_dict['id'] = tag.id tag = d.table_dict_save(tag_dict, model.Tag, context) return tag
def _unfollow(context: Context, data_dict: DataDict, schema: Schema, FollowerClass: Type['ModelFollowingModel[Any, Any]']): model = context['model'] if not context.get('user'): raise ckan.logic.NotAuthorized( _("You must be logged in to unfollow something.")) userobj = model.User.get(context['user']) if not userobj: raise ckan.logic.NotAuthorized( _("You must be logged in to unfollow something.")) follower_id = userobj.id validated_data_dict, errors = validate(data_dict, schema, context) if errors: raise ValidationError(errors) object_id = validated_data_dict.get('id') follower_obj = FollowerClass.get(follower_id, object_id) if follower_obj is None: raise NotFound( _('You are not following {0}.').format(data_dict.get('id'))) follower_obj.delete() model.repo.commit()
def package_id_not_changed(value: Any, context: Context) -> Any: package = context.get('package') if package and value != package.id: raise Invalid('Cannot change value of key from %s to %s. ' 'This key is read-only' % (package.id, value)) return value
def extra_key_not_in_root_schema(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: for schema_key in context.get('schema_keys', []): if schema_key == data[key]: raise Invalid(_('There is a schema field with the same name'))
def package_name_validator(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: model = context['model'] session = context['session'] package = context.get('package') query = session.query(model.Package.id).filter( model.Package.name == data[key], model.Package.state != State.DELETED, ) if package: package_id: Union[Optional[str], Missing] = package.id else: package_id = data.get(key[:-1] + ('id', )) if package_id and package_id is not missing: query = query.filter(model.Package.id != package_id) if session.query(query.exists()).scalar(): errors[key].append(_('That URL is already in use.')) value = data[key] if len(value) < PACKAGE_NAME_MIN_LENGTH: raise Invalid( _('Name "%s" length is less than minimum %s') % (value, PACKAGE_NAME_MIN_LENGTH)) if len(value) > PACKAGE_NAME_MAX_LENGTH: raise Invalid( _('Name "%s" length is more than maximum %s') % (value, PACKAGE_NAME_MAX_LENGTH))
def vocabulary_id_not_changed(value: Any, context: Context) -> Any: vocabulary = context.get('vocabulary') if vocabulary and value != vocabulary.id: raise Invalid( _('Cannot change value of key from %s to %s. ' 'This key is read-only') % (vocabulary.id, value)) return value
def vocabulary_update(context: Context, data_dict: DataDict) -> ActionResult.VocabularyUpdate: '''Update a tag vocabulary. You must be a sysadmin to update vocabularies. For further parameters see :py:func:`~ckan.logic.action.create.vocabulary_create`. :param id: the id of the vocabulary to update :type id: string :returns: the updated vocabulary :rtype: dictionary ''' model = context['model'] vocab_id = data_dict.get('id') if not vocab_id: raise ValidationError({'id': _('id not in data')}) vocab = model.Vocabulary.get(vocab_id) if vocab is None: raise NotFound(_('Could not find vocabulary "%s"') % vocab_id) data_dict['id'] = vocab.id if 'name' in data_dict: if data_dict['name'] == vocab.name: del data_dict['name'] _check_access('vocabulary_update', context, data_dict) schema = context.get( 'schema') or schema_.default_update_vocabulary_schema() data, errors = _validate(data_dict, schema, context) if errors: model.Session.rollback() raise ValidationError(errors) updated_vocab = model_save.vocabulary_dict_update(data, context) if not context.get('defer_commit'): model.repo.commit() return model_dictize.vocabulary_dictize(updated_vocab, context)
def extra_key_not_in_root_schema(key: FlattenKey, data: FlattenDataDict, errors: FlattenErrorDict, context: Context) -> Any: """Ensures that extras are not duplicating base fields """ for schema_key in context.get('schema_keys', []): if schema_key == data[key]: raise Invalid(_('There is a schema field with the same name'))
def package_id_not_changed(value: Any, context: Context) -> Any: """Ensures that package's ID is not changed during the update. """ package = context.get('package') if package and value != package.id: raise Invalid('Cannot change value of key from %s to %s. ' 'This key is read-only' % (package.id, value)) return value
def vocabulary_id_not_changed(value: Any, context: Context) -> Any: """Ensures that vocabulary ID is not changed during the update. """ vocabulary = context.get('vocabulary') if vocabulary and value != vocabulary.id: raise Invalid( _('Cannot change value of key from %s to %s. ' 'This key is read-only') % (vocabulary.id, value)) return value
def api_token_dictize(api_token: model.ApiToken, context: Context) -> dict[str, Any]: include_plugin_extras = context.get(u'include_plugin_extras', False) result_dict = d.table_dictize(api_token, context) plugin_extras = result_dict.pop(u'plugin_extras', None) if include_plugin_extras: result_dict[u'plugin_extras'] = copy.deepcopy( plugin_extras) if plugin_extras else plugin_extras return result_dict
def group_dict_save(group_dict: dict[str, Any], context: Context, prevent_packages_update: bool = False) -> 'model.Group': from ckan.lib.search import rebuild model = context["model"] session = context["session"] group = context.get("group") Group = model.Group if group: group_dict["id"] = group.id group = d.table_dict_save(group_dict, Group, context) if not group.id: group.id = str(uuid.uuid4()) context['group'] = group # Under the new org rules we do not want to be able to update datasets # via group edit so we need a way to prevent this. It may be more # sensible in future to send a list of allowed/disallowed updates for # groups, users, tabs etc. if not prevent_packages_update: pkgs_edited = group_member_save(context, group_dict, 'packages') else: pkgs_edited: dict[str, list[Any]] = {'added': [], 'removed': []} group_users_changed = group_member_save(context, group_dict, 'users') group_groups_changed = group_member_save(context, group_dict, 'groups') group_tags_changed = group_member_save(context, group_dict, 'tags') log.debug( 'Group save membership changes - Packages: %r Users: %r ' 'Groups: %r Tags: %r', pkgs_edited, group_users_changed, group_groups_changed, group_tags_changed) extras = group_dict.get("extras", []) new_extras = {i['key'] for i in extras} if extras: old_extras = group.extras for key in set(old_extras) - new_extras: del group.extras[key] for x in extras: if 'deleted' in x and x['key'] in old_extras: del group.extras[x['key']] continue group.extras[x['key']] = x['value'] # We will get a list of packages that we have either added or # removed from the group, and trigger a re-index. package_ids = pkgs_edited['removed'] package_ids.extend(pkgs_edited['added']) if package_ids: session.commit() [rebuild(package_id) for package_id in package_ids] return group
def package_collaborator_list_for_user(context: Context, data_dict: DataDict) -> AuthResult: '''Checks if a user is allowed to list all datasets a user is a collaborator in The current implementation restricts to the own users themselves. ''' user_obj = context.get('auth_user_obj') if user_obj and data_dict.get('id') in (user_obj.name, user_obj.id): return {'success': True} return {'success': False}
def package_api_to_dict(api1_dict: dict[str, Union[str, Collection[str]]], context: Context) -> dict[str, Any]: package = context.get("package") api_version = context.get('api_version') assert api_version, 'No api_version supplied in context' dictized: dict[str, Any] = {} for key, value in api1_dict.items(): new_value = value if key == 'tags': if isinstance(value, str): new_value = [{"name": item} for item in value.split()] else: new_value = [{"name": item} for item in value] if key == 'extras': updated_extras: dict[str, Any] = {} if package: updated_extras.update(package.extras) assert isinstance(value, dict) updated_extras.update(value) new_value = [] for extras_key, extras_value in updated_extras.items(): new_value.append({"key": extras_key, "value": extras_value}) if key == 'groups' and len(value): if api_version == 1: new_value = [{'name': item} for item in value] else: new_value = [{'id': item} for item in value] dictized[key] = new_value download_url = dictized.pop('download_url', None) if download_url and not dictized.get('resources'): dictized["resources"] = [{'url': download_url}] download_url = dictized.pop('download_url', None) return dictized
def task_status_dict_save(task_status_dict: dict[str, Any], context: Context) -> 'model.TaskStatus': model = context["model"] task_status = context.get("task_status") if task_status: task_status_dict["id"] = task_status.id task_status = d.table_dict_save(task_status_dict, model.TaskStatus, context) return task_status
def unfollow_group(context: Context, data_dict: DataDict) -> None: '''Stop following a group. :param id: the id or name of the group to stop following :type id: string ''' schema = context.get('schema', ckan.logic.schema.default_follow_group_schema()) _unfollow(context, data_dict, schema, context['model'].UserFollowingGroup)