Beispiel #1
0
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}
Beispiel #2
0
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)
Beispiel #3
0
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
Beispiel #4
0
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
Beispiel #5
0
        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)
Beispiel #6
0
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)
Beispiel #7
0
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)
Beispiel #8
0
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)
Beispiel #9
0
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
Beispiel #10
0
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)
Beispiel #11
0
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}
Beispiel #12
0
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}
Beispiel #13
0
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)
Beispiel #14
0
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)
Beispiel #15
0
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
Beispiel #16
0
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()
Beispiel #17
0
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
Beispiel #18
0
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'))
Beispiel #19
0
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))
Beispiel #20
0
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
Beispiel #21
0
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)
Beispiel #22
0
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'))
Beispiel #23
0
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
Beispiel #24
0
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
Beispiel #25
0
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
Beispiel #26
0
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
Beispiel #27
0
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}
Beispiel #28
0
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
Beispiel #29
0
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
Beispiel #30
0
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)