Exemple #1
0
    def _aux_filter(cls, model: Type['CremeEntity'],
                    sc_sequence: Sequence['SetCredentials'], user,
                    queryset: QuerySet, perm: int) -> QuerySet:
        allowed_ctype_ids = {None, ContentType.objects.get_for_model(model).id}
        ESET_ALL = cls.ESET_ALL
        ESET_OWN = cls.ESET_OWN

        forbidden, allowed = split_filter(
            lambda sc: sc.forbidden,
            sorted(
                (sc for sc in sc_sequence
                 if sc.ctype_id in allowed_ctype_ids and sc.value & perm),
                # NB: we sort to get ESET_ALL creds before ESET_OWN ones, then ESET_FILTER ones.
                key=lambda sc: sc.set_type,
            ))

        if not allowed:
            return queryset.none()

        if any(f.set_type == ESET_ALL for f in forbidden):
            return queryset.none()

        def user_filtering_kwargs():  # TODO: cache/lazy
            teams = user.teams
            return {'user__in': [user, *teams]} if teams else {'user': user}

        filtered_qs = queryset

        q = Q()
        for cred in allowed:
            set_type = cred.set_type

            if set_type == ESET_ALL:
                break

            if set_type == ESET_OWN:
                q |= Q(**user_filtering_kwargs())
            else:  # SetCredentials.ESET_FILTER
                # TODO: distinct ? (see EntityFilter.filter())
                q |= cred.efilter.get_q(user=user)
        else:
            filtered_qs = filtered_qs.filter(q)

        for cred in forbidden:
            if cred.set_type == ESET_OWN:
                filtered_qs = filtered_qs.exclude(**user_filtering_kwargs())
            else:  # SetCredentials.ESET_FILTER
                filtered_qs = filtered_qs.exclude(
                    cred.efilter.get_q(user=user))

        return filtered_qs
Exemple #2
0
    def filter(self, _source: QuerySet, ids=None, **kwargs) -> QuerySet:
        if not self.Meta.is_public and not self.ctx.user.is_authenticated:
            _source = _source.none()

        if ids is not None:
            _source = _source.filter(id__in=ids)

        return _source
Exemple #3
0
def retry_qs(qs: QuerySet, **kwargs):
    for delay in (0.5, 1, 2, 4, None):
        result = qs.filter(**kwargs)
        if result.exists():
            return result
        else:
            if delay:
                time.sleep(delay)
            else:
                return qs.none()
Exemple #4
0
        def filter_queryset(self, request: Request, queryset: QuerySet, view: View) -> QuerySet:
            if request.user.role == RoleEnum.USER:
                return queryset.filter(id=request.user.id)
            elif request.user.role == RoleEnum.MANAGER:
                return queryset.filter(Q(role=int(RoleEnum.USER)) | Q(role=int(RoleEnum.MANAGER)))
            elif request.user.role == RoleEnum.ADMIN:
                return queryset
            else:
                self._logger.error(f'Unknown role {request.user.role}')

                return queryset.none()
Exemple #5
0
    def list_resolver(django_object_type, resolver, root, info, **args):
        queryset = maybe_queryset(resolver(root, info, **args))
        if queryset is None:
            queryset = QuerySet.none()  # FIXME: This will throw error

        if isinstance(queryset, QuerySet):
            if hasattr(django_object_type, 'get_queryset'):
                # Pass queryset to the DjangoObjectType get_queryset method
                queryset = maybe_queryset(
                    django_object_type.get_queryset(queryset, info))
        return queryset
Exemple #6
0
    def filter(self, user, queryset: QuerySet, perm: int) -> QuerySet:
        """Filter a QuerySet of CremeEntities by the credentials related to this role.
        Beware, the model class must be a child class of CremeEntity,
        but cannot be CremeEntity itself.

        @param user: A <django.contrib.auth.get_user_model()> instance (eg: CremeUser) ;
                     should be related to the UserRole instance.
        @param queryset: A Queryset on a child class of CremeEntity.
        @param perm: A value in (EntityCredentials.VIEW, EntityCredentials.CHANGE etc...).
        @return: A new (filtered) queryset on the same model.
        """
        model = queryset.model
        assert issubclass(model, CremeEntity)
        assert model is not CremeEntity

        if self.is_app_allowed_or_administrable(model._meta.app_label):
            queryset = SetCredentials.filter(self._get_setcredentials(), user,
                                             queryset, perm)
        else:
            queryset = queryset.none()

        return queryset
Exemple #7
0
def search_escaped_and_unescaped(super_obj: admin.ModelAdmin, request,
                                 input_queryset: QuerySet, search_term: str):
    """
    Can be called from the ``get_search_results()`` method of ``ModelAdmin`` classes, to search using both escaped and unescaped characters.
    For example, passing in "grøt" as ``search_term``, will search for both "grøt" and "gr&oslash;t".
    """
    # `use_distinct` starts as `False` in Django's `get_search_results()`
    combined_searched_querysets, use_distinct_result = input_queryset.none(
    ), False
    # Try both with and without escaping:
    for search_term_repr in (search_term,
                             escape_to_named_characters(search_term)):
        searched_queryset, use_distinct = super_obj.get_search_results(
            request, input_queryset, search_term_repr)
        combined_searched_querysets = combined_searched_querysets.union(
            searched_queryset.order_by())  # clear ordering
        use_distinct_result |= use_distinct

    result_queryset = input_queryset.filter(
        pk__in={cb.pk
                for cb in combined_searched_querysets})
    return result_queryset, use_distinct_result
Exemple #8
0
    def filter_node(self, queryset: QuerySet):
        is_query_all = self.get_query_param('all', True)
        node_id = self.get_query_param('node_id')
        node_name = self.get_query_param('node')
        if node_id:
            _nodes = Node.objects.filter(pk=node_id)
        elif node_name:
            _nodes = Node.objects.filter(value=node_name)
        else:
            return queryset
        if not _nodes:
            return queryset.none()

        node = _nodes.first()

        if not is_query_all:
            queryset = queryset.filter(nodes=node)
            return queryset
        nodeids = node.get_ancestors(with_self=True).values_list('id', flat=True)
        nodeids = list(nodeids)

        queryset = queryset.filter(nodes__in=nodeids)
        return queryset
Exemple #9
0
    def filter_queryset(
        self, queryset: QuerySet, feature_type: FeatureType
    ) -> QuerySet:
        """Apply the filters and lookups to the queryset.

        :param queryset: The queryset to filter.
        :param feature_type: The feature type that the queryset originated from.
        """
        if self.is_empty:
            return queryset.none()

        if self.extra_lookups:
            # Each time an expression node calls add_extra_lookup(),
            # the parent should have used apply_extra_lookups()
            raise RuntimeError("apply_extra_lookups() was not called")

        # All are applied at once.
        if self.annotations:
            queryset = queryset.annotate(**self.annotations)

        lookups = self.lookups
        try:
            lookups += self.typed_lookups[feature_type.name]
        except KeyError:
            pass

        if lookups:
            queryset = queryset.filter(*lookups)

        if self.ordering:
            queryset = queryset.order_by(*self.ordering)

        if self.distinct:
            queryset = queryset.distinct()

        return queryset
Exemple #10
0
    def filter_entities(cls,
                        sc_sequence: Sequence['SetCredentials'],
                        user,
                        queryset: QuerySet,
                        perm: int,
                        models: Iterable[Type['CremeEntity']],
                        as_model=None) -> QuerySet:
        """Filter a queryset of entities with the given credentials.
        Beware, model class must be CremeEntity ; it cannot be a child class
        of CremeEntity.

        @param sc_sequence: A sequence of SetCredentials instances.
        @param user: A django.contrib.auth.get_user_model() instance (eg: CremeUser).e.
        @param queryset: Queryset with model=CremeEntity.
        @param perm: A value in (EntityCredentials.VIEW, EntityCredentials.CHANGE etc...).
        @param models: An iterable of CremeEntity-child-classes, corresponding
               to allowed models.
        @param as_model: A model inheriting CremeEntity, or None. If a model is
               given, all the entities in the queryset are filtered with the
               credentials for this model.
               BEWARE: you should probably use this feature only if the queryset
               if already filtered by its field 'entity_type' (to keep only
               entities of the right model, & so do not make mistakes with credentials).
        @return: A new queryset on CremeEntity.
        @raise: EntityCredentials.FilteringError if an EntityFilter which cannot
                be used on CremeEntity is found in <sc_sequence>.
        """
        assert queryset.model is CremeEntity

        get_for_model = ContentType.objects.get_for_model

        def _check_efilters(sc_seq):
            if any(sc.efilter_id and not sc.efilter.applicable_on_entity_base
                   for sc in sc_seq):
                raise EntityCredentials.FilteringError(
                    "An EntityFilter (not targeting CremeEntity) is used by a "
                    "{cls} instance so it's not possible to use "
                    "{cls}.filter_entities().".format(cls=cls.__name__))

        if as_model is not None:
            assert issubclass(as_model, CremeEntity)

            narrowed_ct_ids = {None, get_for_model(as_model).id}
            narrowed_sc = [
                sc for sc in sc_sequence if sc.ctype_id in narrowed_ct_ids
            ]
            _check_efilters(narrowed_sc)

            return cls._aux_filter(
                model=as_model,
                sc_sequence=narrowed_sc,
                user=user,
                queryset=queryset,
                perm=perm,
            )

        all_ct_ids = {
            None,
            *(get_for_model(model).id for model in models),
        }
        sorted_sc = sorted(
            (sc for sc in sc_sequence if sc.ctype_id in all_ct_ids),
            # NB: we sort to get ESET_ALL creds before ESET_OWN/ESET_FILTER ones.
            key=lambda sc: sc.set_type,
        )
        _check_efilters(sorted_sc)

        # NB: some explanations on the algorithm :
        #  we try to regroup ContentTypes (corresponding to CremeEntity sub_classes)
        #  which have the same filtering rules ; so we can generate a Query which looks like
        #    entity_type__in=[...] OR (entity_type__in=[...] AND user__exact=current-user) OR
        #    (entity_type__in=[...] AND field1__startswith='foo')

        OWN_FILTER_ID = 0  # Fake EntityFilter ID corresponding to ESET_OWN.

        ESET_ALL = cls.ESET_ALL
        ESET_OWN = cls.ESET_OWN
        ESET_FILTER = cls.ESET_FILTER

        def _extract_filter_ids(set_creds):
            for sc in set_creds:
                if sc.set_type == ESET_OWN:
                    yield OWN_FILTER_ID
                    break  # Avoid several OWN_FILTER_ID (should not happen)

            for sc in set_creds:
                if sc.set_type == ESET_FILTER:
                    yield sc.efilter_id

        # Map of EntityFilters to apply on ContentTypes groups
        #   key = tuple containing 2 tuples of filter IDs: forbidden rules & allowed ones.
        #   value = list of ContentType IDs.
        #  Note: special values for EntityFilter ID:
        #    None: means ESET_ALL (no filtering)
        #    OWN_FILTER_ID: means ESET_OWN (a virtual EntityFilter on "user" field).
        ctypes_filtering: DefaultDict[tuple, List[int]] = defaultdict(list)

        efilters_per_id = {sc.efilter_id: sc.efilter for sc in sc_sequence}

        for model in models:
            ct_id = get_for_model(model).id
            model_ct_ids = {None, ct_id}  # <None> means <CremeEntity>

            forbidden, allowed = split_filter(
                lambda sc: sc.forbidden,
                (sc for sc in sorted_sc
                 if sc.ctype_id in model_ct_ids and sc.value & perm))

            if allowed:
                if forbidden and forbidden[0].set_type == ESET_ALL:
                    continue

                allowed_filter_ids = [None] if allowed[0].set_type == ESET_ALL else \
                                     [*_extract_filter_ids(allowed)]
                forbidden_filter_ids = [*_extract_filter_ids(forbidden)]

                ctypes_filtering[(
                    tuple(forbidden_filter_ids),
                    tuple(allowed_filter_ids),
                )].append(ct_id)

        if not ctypes_filtering:
            queryset = queryset.none()
        else:

            def _user_filtering_q():  # TODO: cached/lazy ?
                teams = user.teams
                return Q(
                    **
                    {'user__in': [user, *teams]} if teams else {'user': user})

            def _efilter_ids_to_Q(efilter_ids):
                filters_q = Q()

                for filter_id in efilter_ids:
                    # TODO: condexpr
                    if filter_id is not None:  # None == ESET_ALL
                        if filter_id == OWN_FILTER_ID:
                            filter_q = _user_filtering_q()
                        else:
                            # TODO: distinct ??
                            filter_q = efilters_per_id[filter_id].get_q(
                                user=user)

                        filters_q |= filter_q

                return filters_q

            q = Q()
            for (forbidden_filter_ids,
                 allowed_filter_ids), ct_ids in ctypes_filtering.items():
                q |= ((Q(entity_type_id=ct_ids[0]) if len(ct_ids) == 1 else Q(
                    entity_type_id__in=ct_ids))
                      & _efilter_ids_to_Q(allowed_filter_ids)
                      & ~_efilter_ids_to_Q(forbidden_filter_ids))

            queryset = queryset.filter(q)

        return queryset
Exemple #11
0
 def filter_queryset(self, request: Request, queryset: QuerySet,
                     view: View) -> QuerySet:
     if request.user.is_authenticated:
         return queryset.filter(author=request.user)
     else:
         return queryset.none()