Example #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}
Example #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)
Example #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
Example #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
Example #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)
Example #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)
Example #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)
Example #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)
Example #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
Example #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)
Example #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}
Example #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}
Example #13
0
File: patch.py Project: frafra/ckan
def group_patch(context: Context,
                data_dict: DataDict) -> ActionResult.GroupPatch:
    '''Patch a group

    :param id: the id or name of the group
    :type id: string

    The difference between the update and patch methods is that the patch will
    perform an update of the provided parameters, while leaving all other
    parameters unchanged, whereas the update methods deletes all parameters
    not explicitly provided in the data_dict
    '''
    _check_access('group_patch', context, data_dict)

    show_context: Context = {
        'model': context['model'],
        'session': context['session'],
        'user': context['user'],
        'auth_user_obj': context['auth_user_obj'],
    }

    group_dict = _get_action('group_show')(show_context, {
        'id': _get_or_bust(data_dict, 'id')
    })

    patched = dict(group_dict)
    patched.pop('display_name', None)
    patched.update(data_dict)

    patch_context = context.copy()
    patch_context['allow_partial_update'] = True
    return _update.group_update(patch_context, patched)
Example #14
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)
Example #15
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)
Example #16
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
Example #17
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()
Example #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'))
Example #19
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
Example #20
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
Example #21
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))
Example #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'))
Example #23
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)
Example #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
Example #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
Example #26
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
Example #27
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
Example #28
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}
Example #29
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
Example #30
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