Пример #1
0
    def test_update_raw_node_relationship(self):
        group1, group2 = Group.objects.create(
            name='group1'), Group.objects.create(name='group2')
        node1, node2 = get_node_for_object(group1), get_node_for_object(group2)

        # Create a custom relationship between group1 and group2
        results, _ = db.cypher_query(
            'MATCH (a), (b) WHERE ID(a) = %d AND ID(b) = %d '
            'CREATE (a)-[r1:RELATION]->(b), (b)-[r2:RELATION]->(a) '
            'RETURN r1, r2' % (node1.id, node2.id))
        results = list(flatten(results))
        self.assertEqual(len(results), 2)
        self.assertTrue(all([r.type == 'RELATION' for r in results]))

        node1.__update_raw_node__()

        # Make sure custom relationship is deleted
        results, _ = db.cypher_query(
            'MATCH (n)-[r]->() WHERE ID(n) = %d RETURN r' % node1.id)
        self.assertEqual(len(results), 0)

        # The other relationship should still be intact
        results, _ = db.cypher_query(
            'MATCH (n)-[r]->() WHERE ID(n) = %d RETURN r' % node2.id)
        results = list(flatten(results))
        self.assertEqual(len(results), 1)
Пример #2
0
    def test_inflate_node_class_raises_inflate_error(self):
        # Make sure the InflateError is raised if trying
        # to inflate nodes with wrong model
        author = AuthorFixture(Author).create_one()
        deflated, _ = list(
            flatten(
                db.cypher_query('MATCH (u: UserNode {pk: %d}) RETURN u;' %
                                author.user.pk)))

        node_class = get_node_class_for_model(Author)
        self.assertRaises(InflateError, node_class.inflate, deflated)
Пример #3
0
 def _get_id_from_database(self, params):
     """
     Query for node and return id.
     :param params: Parameters to use in query.
     :returns: Node id if found, else None
     """
     query = ' '.join(
         ('MATCH (n:{label}) WHERE'.format(label=self.__label__),
          ' AND '.join([
              'n.{} = {{{}}}'.format(key, key) for key in params.keys()
          ]), 'RETURN id(n) LIMIT 1'))
     result, _ = db.cypher_query(query, params)
     return list(flatten(result))[0] if result else None
Пример #4
0
    def test_update_raw_node_property(self):
        group = Group.objects.create(name='group')
        node = get_node_for_object(group)

        # Set a custom attribute on the node and make sure it's saved on the node
        result, _ = list(
            flatten(
                db.cypher_query('MATCH (n) WHERE ID(n) = %d '
                                'SET n.foo = "bar", n.baz = "qux" RETURN n' %
                                node.id)))
        self.assertTrue(all(i in result.properties for i in ('foo', 'baz')))
        self.assertEqual(result.properties['foo'], 'bar')
        self.assertEqual(result.properties['baz'], 'qux')

        self.assertFalse(all(hasattr(node, i) for i in ('foo', 'baz')))
        node.__update_raw_node__()

        # Make sure the custom attribute has been deleted
        result, _ = list(
            flatten(
                db.cypher_query('MATCH (n) WHERE ID(n) = %d RETURN n' %
                                node.id)))
        self.assertFalse(all(i in result.properties for i in ('foo', 'baz')))
Пример #5
0
    def list(self, request, format=None):
        """
        Returns the meta graph serialized as JSON.
        """
        result, _ = db.cypher_query(
            'MATCH (n {type: "MetaNode", is_intermediary: False}) RETURN n')

        response = []
        for item in list(flatten(result)):
            try:
                if hasattr(item, 'properties') and all(
                        value in item.properties
                        for value in ('app_label', 'model_name')):
                    model = apps.get_model(
                        app_label=item.properties['app_label'],
                        model_name=item.properties['model_name'])
                    klass = get_meta_node_class_for_model(model)
                    serializer = NodeSerializer(instance=klass.inflate(item),
                                                many=False)
                    response.append(serializer.data)
            except LookupError:
                # Might trigger if getting data originating from a different source.
                continue
        return Response(data=response, content_type='application/json')
Пример #6
0
def get_objects_for_user(user,
                         permissions,
                         klass=None,
                         use_groups=True,
                         extra_perms=None,
                         any_perm=False,
                         with_superuser=True):
    """
    Returns a queryset of objects for which there can be calculated a path between
    the ``user`` using one or more access rules with *all* permissions present
    at ``permissions``.
    
    :param user: ``User`` instance for which objects should be returned.
    :param permissions: Single permission string, or sequence of permission
      strings that should be checked.
      If ``klass`` parameter is not given, those should be full permission
      strings rather than only codenames (ie. ``auth.change_user``). If more than
      one permission is present in the sequence, their content type **must** be 
      the same or ``MixedContentTypeError`` would be raised.
    :param klass: May be a ``Model``, ``Manager`` or ``QuerySet`` object. If not
      given, this will be calculated based on passed ``permissions`` strings.
    :param use_groups: If ``True``, include users groups permissions.
      Defaults to ``True``.
    :param extra_perms: Single permission string, or sequence of permission strings
      that should be used as ``global_perms`` base. These permissions will be 
      treated as if the user possesses them.
    :param any_perm: If ``True``, any permission in sequence is accepted. 
      Defaults to ``False``.
    :param with_superuser: If ``True`` and ``user.is_superuser`` is set, returns
      the entire queryset. Otherwise will only return the objects the user has 
      explicit permissions to. Defaults to ``True``.
     
    :raises MixedContentTypeError: If computed content type for ``permissions``
      and/or ``klass`` clashes.
    :raises ValueError: If unable to compute content type for ``permissions``.
    
    :returns: QuerySet containing objects ``user`` has ``permissions`` to.
    """
    # Make sure all permissions checks out!
    ctype, codenames = check_permissions_app_label(permissions)
    if extra_perms:
        extra_ctype, extra_perms = check_permissions_app_label(extra_perms)
        if extra_ctype != ctype:
            raise MixedContentTypeError(
                'Calculated content type from keyword argument `extra_perms` '
                '%s does not match %r.' % (extra_ctype, ctype))
    extra_perms = extra_perms or set()

    if ctype is None and klass is not None:
        queryset = _get_queryset(klass)
        ctype = get_content_type(queryset.model)
    elif ctype is not None and klass is None:
        queryset = _get_queryset(ctype.model_class())
    elif klass is None:
        raise ValueError('Could not determine the content type.')
    else:
        queryset = _get_queryset(klass)
        if ctype.model_class() != queryset.model:
            raise MixedContentTypeError(
                'ContentType for given permissions and klass differs.')

    # Superusers have access to all objects.
    if with_superuser and user.is_superuser:
        return queryset

    # We don't support anonymous users.
    if user.is_anonymous:
        return queryset.none()

    # If there is no node in the graph for the user object, return empty queryset.
    source_node = get_node_class_for_model(user).nodes.get_or_none(
        **{'pk': user.pk})
    if not source_node:
        return queryset.none()

    # Next, get all permissions the user has, either directly set through user permissions
    # or if ``use_groups`` are set, derived from a group membership.
    global_perms = extra_perms | set(
        get_perms(user, queryset.model
                  ) if use_groups else get_user_perms(user, queryset.model))

    # Check if we requires the user to have *all* permissions or if it is
    # sufficient with any provided.
    if not any_perm and not all((code in global_perms for code in codenames)):
        return queryset.none()
    elif any_perm:
        for code in codenames.copy():
            if code not in global_perms:
                codenames.remove(code)

    # Calculate a PATH query for each rule
    queries = []
    for access_rule in get_access_rules(get_content_type(user), ctype,
                                        codenames):
        manager = source_node.paths
        for n, rule_definition in enumerate(access_rule.relation_types_obj):
            relation_type, target_props = zip(*rule_definition.items())
            relation_type, target_props = relation_type[0], target_props[0]

            source_props = {}
            if n == 0 and access_rule.requires_staff:
                source_props.update({'is_staff': True})
            manager = manager.add(relation_type,
                                  source_props=source_props,
                                  target_props=target_props)

        if manager.statement:
            queries.append(manager.get_path())

    q_values = Q()
    start_node_class = get_node_class_for_model(user)
    end_node_class = get_node_class_for_model(queryset.model)
    for query in queries:
        # FIXME: https://github.com/inonit/libcypher-parser-python/issues/1
        # validate_cypher(query, raise_exception=True)
        result, _ = db.cypher_query(query)
        if result:
            values = set()
            for item in flatten(result):
                if not isinstance(item, Path):  # pragma: no cover
                    continue
                elif (start_node_class.__label__ not in item.start.labels
                      or end_node_class.__label__ not in item.end.labels):
                    continue
                try:
                    start, end = (start_node_class(user).inflate(item.start),
                                  end_node_class.inflate(item.end))
                    if start == source_node and isinstance(
                            end, end_node_class):
                        values.add(item.end.properties['pk'])
                except (KeyError, InflateError):  # pragma: no cover
                    continue
            q_values |= Q(pk__in=values)

    # If no values in the Q filter, it means we couldn't get a path from the
    # user node to given object in queryset by any evaluated rule.
    # Return an empty queryset.
    if not q_values:
        return queryset.none()

    return queryset.filter(q_values)
Пример #7
0
def get_users_with_perms(obj,
                         permissions,
                         with_superusers=False,
                         with_group_users=True):
    """
    Returns a queryset of all ``User`` objects which there can be calculated a path from
    the given ``obj``.

    :param obj: model instance.
    :param permissions: Single permission string, or sequence of permissions strings
      that user requires to have.
    :param with_superusers: Default: ``False``. If set to ``True`` result would
      include all superusers.
    :param with_group_users: Default: ``True``. If set to ``False`` result would
      **not** include users which has only group permissions for given ``obj``.

    :raises MixedContentTypeError: If computed content type for ``permissions``
      and/or ``obj`` clashes.

    :returns: Queryset containing ``User`` objects which has ``permissions`` for ``obj``.
    """
    ctype, codenames = check_permissions_app_label(permissions)
    if ctype is None:
        ctype = get_content_type(obj)
        if codenames:
            # Make sure permissions are valid.
            _codenames = set(
                ctype.permission_set.filter(
                    codename__in=codenames).values_list('codename', flat=True))
            if not codenames == _codenames:
                message = ngettext_lazy(
                    'Calculated content type from permission "%s" does not match %r.'
                    % (next(iter(codenames)), ctype),
                    'One or more permissions "%s" from calculated content type does not match %r.'
                    % (', '.join(sorted(codenames)), ctype), len(codenames))
                raise MixedContentTypeError(message)

    elif not ctype == get_content_type(obj):
        raise MixedContentTypeError(
            'Calculated content type %r does not match %r.' %
            (ctype, get_content_type(obj)))

    queryset = _get_queryset(User)

    # If there is no node in the graph for ``obj``, return empty queryset.
    target_node = get_node_class_for_model(obj).nodes.get_or_none(
        **{'pk': obj.pk})
    if not codenames or not target_node:
        if with_superusers is True:
            return queryset.filter(is_superuser=True)
        return queryset.none()

    ctype_source = get_content_type(User)

    # We need a fake source content type model to use as origin.
    fake_model = ctype_source.model_class()()
    source_node = get_node_for_object(fake_model, bind=False)

    queries = []
    for access_rule in get_access_rules(ctype_source, ctype, codenames):
        manager = source_node.paths
        if access_rule.direction is not None:
            manager.direction = access_rule.direction

        for n, rule_definition in enumerate(access_rule.relation_types_obj):
            relation_type, target_props = zip(*rule_definition.items())
            relation_type, target_props = relation_type[0], target_props[0]

            source_props = {}
            target_props = target_props or {}

            if n == 0 and access_rule.requires_staff:
                source_props.update({'is_staff': True})

            # Make sure the last object in the query is matched to ``obj``.
            if n == len(access_rule.relation_types_obj) - 1:
                target_props['pk'] = target_node.pk

            # FIXME: Workaround for https://github.com/inonit/django-chemtrails/issues/46
            # If using "{source}.<attr>" filters, ignore them!
            target_props = {
                key: value
                for key, value in target_props.items()
                if isinstance(value, str) and not value.startswith('{source}.')
            }
            manager = manager.add(relation_type,
                                  source_props=source_props,
                                  target_props=target_props)

        if manager.statement:
            queries.append(manager.get_path())

    q_values = Q()
    if with_superusers is True:
        q_values |= Q(is_superuser=True)

    start_node_class = get_node_class_for_model(queryset.model)
    end_node_class = get_node_class_for_model(obj)
    for query in queries:
        # FIXME: https://github.com/inonit/libcypher-parser-python/issues/1
        # validate_cypher(query, raise_exception=True)
        result, _ = db.cypher_query(query)
        if result:
            values = set()
            for item in flatten(result):
                if not isinstance(item, Path):  # pragma: no cover
                    continue
                elif (start_node_class.__label__ not in item.start.labels
                      or end_node_class.__label__ not in item.end.labels):
                    continue
                try:
                    start, end = (start_node_class.inflate(item.start),
                                  end_node_class.inflate(item.end))
                    if isinstance(start,
                                  start_node_class) and end == target_node:
                        # Make sure the user object has correct permissions
                        instance = start.get_object()
                        global_perms = set(
                            get_perms(instance, obj) if with_group_users else
                            get_user_perms(instance, obj))
                        if all((code in global_perms for code in codenames)):
                            values.add(item.start.properties['pk'])
                except (KeyError, InflateError, ObjectDoesNotExist):
                    continue
            q_values |= Q(pk__in=values)

    if not q_values:
        return queryset.none()

    return queryset.filter(q_values)