Example #1
0
    def _get_subscribed(cls, user):
        # Of the public objects, determine to which the user has subscribed
        if is_user_anonymous(user):
            user = get_anonymous_user()

        return UserAssetSubscription.objects.filter(
            asset__in=cls._get_publics(), user=user).values('asset')
Example #2
0
def get_user_permission_assignments(
    affected_object, user, object_permission_assignments
):
    """
    Works like `get_user_permission_assignments_queryset` but returns
    a list instead of a queryset. It also needs a list of all
    `affected_object`'s permission assignments to search for assignments
    `user` is allowed to see.

    Args:
        affected_object (Asset)
        user (User)
        object_permission_assignments (list):
    Returns:
         list

    """
    user_permission_assignments = []
    filtered_user_ids = None

    if not user or is_user_anonymous(user):
        filtered_user_ids = [affected_object.owner_id]
    elif not affected_object.has_perm(user, PERM_MANAGE_ASSET):
        # Display only users' permissions if they are not allowed to modify
        # others' permissions
        filtered_user_ids = [affected_object.owner_id,
                             user.pk,
                             settings.ANONYMOUS_USER_ID]

    for permission_assignment in object_permission_assignments:
        if (filtered_user_ids is None or
                permission_assignment.user_id in filtered_user_ids):
            user_permission_assignments.append(permission_assignment)

    return user_permission_assignments
Example #3
0
    def _get_queryset_for_data_sharing_enabled(self, request: Request,
                                               queryset: QuerySet) -> QuerySet:
        """
        Returns a queryset containing the assets that the user is allowed
        to view data.
        I.e., user needs 'view_submissions' or 'partial_submissions'.
        """

        self._return_queryset = False
        parameters = self.__get_parsed_parameters(request)
        try:
            data_sharing = parameters[self.DATA_SHARING_PARAMETER][0]
        except KeyError:
            return queryset

        if not data_sharing:  # No reason to be False, but just in case
            return queryset

        self._return_queryset = True
        required_perm_ids = get_perm_ids_from_code_names(
            [PERM_VIEW_SUBMISSIONS, PERM_PARTIAL_SUBMISSIONS])
        user = request.user
        if is_user_anonymous(user):
            # Avoid giving anonymous users special treatment when viewing
            # public objects
            perms = ObjectPermission.objects.none()
        else:
            perms = ObjectPermission.objects.filter(
                deny=False, user=user, permission_id__in=required_perm_ids)

        asset_ids = perms.values('asset')

        # `SearchFilter` handles further filtering to include only assets with
        # `data_sharing__enabled` (`self.DATA_SHARING_PARAMETER`) set to true
        return queryset.filter(pk__in=asset_ids)
Example #4
0
    def _get_owned_and_explicitly_shared(user):
        view_asset_perm_id = get_perm_ids_from_code_names(PERM_VIEW_ASSET)
        if is_user_anonymous(user):
            # Avoid giving anonymous users special treatment when viewing
            # public objects
            perms = ObjectPermission.objects.none()
        else:
            perms = ObjectPermission.objects.filter(
                deny=False, user=user, permission_id=view_asset_perm_id)

        return perms.values('asset')
Example #5
0
def assign_applicable_kc_permissions(
    obj: Model,
    user: Union[AnonymousUser, User, int],
    kpi_codenames: Union[str, list]
):
    """
    Assign the `user` the applicable KC permissions to `obj`, if any
    exists, given one KPI permission codename as a single string or many
    codenames as an iterable. If `obj` is not a :py:class:`Asset` or does
    not have a deployment, take no action.
    """
    if not obj._meta.model_name == 'asset':
        return
    permissions = _get_applicable_kc_permissions(obj, kpi_codenames)
    if not permissions:
        return
    xform_id = _get_xform_id_for_asset(obj)
    if not xform_id:
        return

    # Retrieve primary key from user object and use it on subsequent queryset.
    # It avoids loading the object when `user` is passed as an integer.
    if not isinstance(user, int):
        if is_user_anonymous(user):
            user_id = settings.ANONYMOUS_USER_ID
        else:
            user_id = user.pk
    else:
        user_id = user

    if user_id == settings.ANONYMOUS_USER_ID:
        return set_kc_anonymous_permissions_xform_flags(
            obj, kpi_codenames, xform_id)

    xform_content_type = KobocatContentType.objects.get(
        **obj.KC_CONTENT_TYPE_KWARGS)

    kc_permissions_already_assigned = KobocatUserObjectPermission.objects.filter(
        user_id=user.pk, permission__in=permissions, object_pk=xform_id,
    ).values_list('permission__codename', flat=True)
    permissions_to_create = []
    for permission in permissions:
        if permission.codename in kc_permissions_already_assigned:
            continue
        permissions_to_create.append(KobocatUserObjectPermission(
            user_id=user.pk, permission=permission, object_pk=xform_id,
            content_type=xform_content_type
        ))
    KobocatUserObjectPermission.objects.bulk_create(permissions_to_create)
Example #6
0
    def __get_object_permissions(self, deny, user=None, codename=None):
        """
        Returns a set of user ids and object permission ids related to
        object `self`.

        Args:
            deny (bool): If `True`, returns denied permissions
            user (User)
            codename (str)

        Returns:
            set: [(User's pk, Permission's pk)]
        """
        def build_dict(user_id_, object_permissions_):
            perms_ = []
            if object_permissions_:
                for permission_id, codename_, deny_ in object_permissions_:
                    if (deny_ is not deny
                            or codename is not None and codename != codename_):
                        continue
                    perms_.append((user_id_, permission_id))
            return perms_

        perms = []
        # If User is not none, retrieve all permissions for this user
        # grouped by object ids, otherwise, retrieve all permissions for
        # this object grouped by user ids.
        if user is not None:
            # Ensuring that the user has at least anonymous permissions if they
            # have been assigned to the asset
            all_anon_object_permissions = self.__get_all_user_permissions(
                user_id=settings.ANONYMOUS_USER_ID)
            perms = build_dict(
                settings.ANONYMOUS_USER_ID,
                all_anon_object_permissions.get(self.pk),
            )
            if not is_user_anonymous(user):
                all_object_permissions = self.__get_all_user_permissions(
                    user_id=user.pk)
                perms += build_dict(user.pk,
                                    all_object_permissions.get(self.pk))
        else:
            all_object_permissions = self.__get_all_object_permissions(
                object_id=self.pk)
            for user_id, object_permissions in all_object_permissions.items():
                perms += build_dict(user_id, object_permissions)

        return set(perms)
Example #7
0
def remove_applicable_kc_permissions(
    obj: Model,
    user: Union[AnonymousUser, User, int],
    kpi_codenames: Union[str, list]
):
    """
    Remove the `user` the applicable KC permissions from `obj`, if any
    exists, given one KPI permission codename as a single string or many
    codenames as an iterable. If `obj` is not a :py:class:`Asset` or does
    not have a deployment, take no action.
    """

    if not obj._meta.model_name == 'asset':
        return
    permissions = _get_applicable_kc_permissions(obj, kpi_codenames)
    if not permissions:
        return
    xform_id = _get_xform_id_for_asset(obj)
    if not xform_id:
        return

    # Retrieve primary key from user object and use it on subsequent queryset.
    # It avoids loading the object when `user` is passed as an integer.
    if not isinstance(user, int):
        if is_user_anonymous(user):
            user_id = settings.ANONYMOUS_USER_ID
        else:
            user_id = user.pk
    else:
        user_id = user

    if user_id == settings.ANONYMOUS_USER_ID:
        return set_kc_anonymous_permissions_xform_flags(
            obj, kpi_codenames, xform_id, remove=True)

    content_type_kwargs = _get_content_type_kwargs_for_related(obj)
    KobocatUserObjectPermission.objects.filter(
        user_id=user_id, permission__in=permissions, object_pk=xform_id,
        # `permission` has a FK to `ContentType`, but I'm paranoid
        **content_type_kwargs
    ).delete()
Example #8
0
def get_user_permission_assignments_queryset(affected_object, user):
    """
    Returns a queryset to fetch `affected_object`'s permission assignments
    that `user` is allowed to see.

    Args:
        affected_object (Asset)
        user (User)
    Returns:
        QuerySet

    """

    # `affected_object.permissions` is a `GenericRelation(ObjectPermission)`
    # Don't Prefetch `content_object`.
    # See `AssetPermissionAssignmentSerializer.to_representation()`
    queryset = affected_object.permissions.filter(deny=False).select_related(
        'permission', 'user'
    ).order_by(
        'user__username', 'permission__codename'
    ).exclude(permission__codename=PERM_FROM_KC_ONLY).all()

    # Filtering is done in `get_queryset` instead of FilteredBackend class
    # because it's specific to `ObjectPermission`.
    if not user or is_user_anonymous(user):
        queryset = queryset.filter(
            user_id__in=[
                affected_object.owner_id,
                settings.ANONYMOUS_USER_ID,
            ]
        )
    elif not affected_object.has_perm(user, PERM_MANAGE_ASSET):
        # Display only users' permissions if they are not allowed to modify
        # others' permissions
        queryset = queryset.filter(user_id__in=[user.pk,
                                                affected_object.owner_id,
                                                settings.ANONYMOUS_USER_ID])

    return queryset
Example #9
0
 def has_perm(self, user_obj: User, perm: str) -> bool:
     """
     Does `user_obj` have perm on this object? (True/False)
     """
     app_label, codename = perm_parse(perm, self)
     is_anonymous = is_user_anonymous(user_obj)
     user_obj = get_database_user(user_obj)
     # Treat superusers the way django.contrib.auth does
     if user_obj.is_active and user_obj.is_superuser:
         return True
     # Look for matching permissions
     result = len(
         self._get_effective_perms(user=user_obj, codename=codename)) == 1
     if not result and not is_anonymous:
         # The user-specific test failed, but does the public have access?
         result = self.has_perm(AnonymousUser(), perm)
     if result and is_anonymous:
         # Is an anonymous user allowed to have this permission?
         fq_permission = '{}.{}'.format(app_label, codename)
         if fq_permission not in settings.ALLOWED_ANONYMOUS_PERMISSIONS:
             return False
     return result
Example #10
0
    def assign_perm(self,
                    user_obj,
                    perm,
                    deny=False,
                    defer_recalc=False,
                    skip_kc=False,
                    partial_perms=None):
        r"""
            Assign `user_obj` the given `perm` on this object, or break
            inheritance from a parent object. By default, recalculate
            descendant objects' permissions and apply any applicable KC
            permissions.
            :type user_obj: :py:class:`User` or :py:class:`AnonymousUser`
            :param perm: str. The `codename` of the `Permission`
            :param deny: bool. When `True`, break inheritance from parent object
            :param defer_recalc: bool. When `True`, skip recalculating
                descendants
            :param skip_kc: bool. When `True`, skip assignment of applicable KC
                permissions
            :param partial_perms: dict. Filters used to narrow down query for
              partial permissions
        """
        app_label, codename = perm_parse(perm, self)
        assignable_permissions = self.get_assignable_permissions()
        if codename not in assignable_permissions:
            # Some permissions are calculated and not stored in the database
            raise serializers.ValidationError({
                'permission':
                f'{codename} cannot be assigned explicitly to {self}'
            })
        is_anonymous = is_user_anonymous(user_obj)
        user_obj = get_database_user(user_obj)
        if is_anonymous:
            # Is an anonymous user allowed to have this permission?
            fq_permission = f'{app_label}.{codename}'
            if (not deny and fq_permission
                    not in settings.ALLOWED_ANONYMOUS_PERMISSIONS):
                raise serializers.ValidationError({
                    'permission':
                    f'Anonymous users cannot be granted the permission {codename}.'
                })
        perm_model = Permission.objects.get(content_type__app_label=app_label,
                                            codename=codename)
        existing_perms = self.permissions.filter(user=user_obj)
        identical_existing_perm = existing_perms.filter(
            inherited=False,
            permission_id=perm_model.pk,
            deny=deny,
        )
        if identical_existing_perm.exists():
            # We need to always update partial permissions because
            # they may have changed even if `perm` is the same.
            self._update_partial_permissions(user_obj,
                                             perm,
                                             partial_perms=partial_perms)
            # The user already has this permission directly applied
            return identical_existing_perm.first()

        # Remove any explicitly-defined contradictory grants or denials
        contradictory_filters = models.Q(user=user_obj,
                                         permission_id=perm_model.pk,
                                         deny=not deny,
                                         inherited=False)
        if not deny and perm in self.CONTRADICTORY_PERMISSIONS.keys():
            contradictory_filters |= models.Q(
                user=user_obj,
                permission__codename__in=self.CONTRADICTORY_PERMISSIONS.get(
                    perm),
            )
        contradictory_perms = existing_perms.filter(contradictory_filters)
        contradictory_codenames = list(
            contradictory_perms.values_list('permission__codename', flat=True))

        contradictory_perms.delete()
        # Check if any KC permissions should be removed as well
        if deny and not skip_kc:
            remove_applicable_kc_permissions(self, user_obj,
                                             contradictory_codenames)
        # Create the new permission
        new_permission = ObjectPermission.objects.create(
            asset=self,
            user=user_obj,
            permission_id=perm_model.pk,
            deny=deny,
            inherited=False)
        # Assign any applicable KC permissions
        if not deny and not skip_kc:
            assign_applicable_kc_permissions(self, user_obj, codename)
        # Resolve implied permissions, e.g. granting change implies granting
        # view
        implied_perms = self.get_implied_perms(
            codename, reverse=deny,
            for_instance=self).intersection(assignable_permissions)
        for implied_perm in implied_perms:
            self.assign_perm(user_obj,
                             implied_perm,
                             deny=deny,
                             defer_recalc=True)
        # We might have been called by ourselves to assign a related
        # permission. In that case, don't recalculate here.
        if defer_recalc:
            return new_permission

        self._update_partial_permissions(user_obj,
                                         perm,
                                         partial_perms=partial_perms)

        # Recalculate all descendants
        self.recalculate_descendants_perms()
        return new_permission