def test_has_object_permission(self):
     request = RequestFactory().get('/')
     ok_(
         AnyOf(AllowNone,
               AllowAny)().has_object_permission(request, 'myview', None))
     ok_(
         AnyOf(AllowAny,
               AllowNone)().has_object_permission(request, 'myview', None))
Exemple #2
0
class ProductIconViewSet(CORSMixin, MarketplaceView, ListModelMixin,
                         RetrieveModelMixin, GenericViewSet):
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    permission_classes = [
        AnyOf(AllowReadOnly, GroupPermission('ProductIcon', 'Create'))
    ]
    queryset = ProductIcon.objects.all()
    serializer_class = ProductIconSerializer
    cors_allowed_methods = ['get', 'post']
    filter_fields = ('ext_url', 'ext_size', 'size')

    def create(self, request, *args, **kwargs):
        serializer = self.get_serializer(data=request.DATA)
        if serializer.is_valid():
            log.info('Resizing product icon %s @ %s to %s for webpay' %
                     (serializer.data['ext_url'], serializer.data['ext_size'],
                      serializer.data['size']))
            tasks.fetch_product_icon.delay(serializer.data['ext_url'],
                                           serializer.data['ext_size'],
                                           serializer.data['size'])
            return Response(serializer.data, status=status.HTTP_202_ACCEPTED)
        return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
Exemple #3
0
class AppStatsTotal(CORSMixin, SlugOrIdMixin, ListAPIView, StatsTotalBase):
    authentication_classes = (RestOAuthAuthentication,
                              RestSharedSecretAuthentication)
    cors_allowed_methods = ['get']
    permission_classes = [
        AnyOf(AllowAppOwner, GroupPermission('Stats', 'View'))
    ]
    queryset = Webapp.objects.all()
    slug_field = 'app_slug'

    def get(self, request, pk):
        app = self.get_object()
        client = self.get_client()

        # Note: We have to do this as separate requests so that if one fails
        # the rest can still be returned.
        data = {}
        for metric, stat in APP_STATS_TOTAL.items():
            data[metric] = {}
            query = self.get_query(metric, stat['metric'], app.id)

            try:
                resp = client.raw(query)
            except ValueError as e:
                log.info('Received value error from monolith client: %s' % e)
                continue

            self.process_response(resp, data)

        return Response(data)
Exemple #4
0
class FeedAppViewSet(CORSMixin, MarketplaceView, SlugOrIdMixin,
                     ImageURLUploadMixin):
    """
    A viewset for the FeedApp class, which highlights a single app and some
    additional metadata (e.g. a review or a screenshot).
    """
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    permission_classes = [
        AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate'))
    ]
    filter_backends = (OrderingFilter, )
    queryset = FeedApp.objects.all()
    cors_allowed_methods = ('get', 'delete', 'post', 'put', 'patch')
    serializer_class = FeedAppSerializer

    image_fields = (('background_image_upload_url', 'image_hash', ''), )

    def list(self, request, *args, **kwargs):
        page = self.paginate_queryset(self.filter_queryset(
            self.get_queryset()))
        serializer = self.get_pagination_serializer(page)
        return response.Response(serializer.data)
Exemple #5
0
class FeedItemViewSet(viewsets.ModelViewSet):
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]
    permission_classes = [AnyOf(AllowReadOnly,
                                GroupPermission('Feed', 'Curate'))]
    queryset = FeedItem.objects.all()
    serializer_class = FeedItemSerializer
Exemple #6
0
 def get_object(self, queryset=None):
     try:
         app = super(AppViewSet, self).get_object()
     except Http404:
         app = super(AppViewSet, self).get_object(self.get_base_queryset())
         # Owners and reviewers can see apps regardless of region.
         owner_or_reviewer = AnyOf(AllowAppOwner, AllowReviewerReadOnly)
         if owner_or_reviewer.has_object_permission(self.request, self, app):
             return app
         data = {}
         for key in ("name", "support_email", "support_url"):
             value = getattr(app, key)
             data[key] = unicode(value) if value else ""
         data["reason"] = "Not available in your region."
         raise HttpLegallyUnavailable(data)
     self.check_object_permissions(self.request, app)
     return app
Exemple #7
0
 def get_object(self, queryset=None):
     try:
         app = super(AppViewSet, self).get_object()
     except Http404:
         app = super(AppViewSet, self).get_object(self.get_base_queryset())
         # Owners and reviewers can see apps regardless of region.
         owner_or_reviewer = AnyOf(AllowAppOwner, AllowReviewerReadOnly)
         if owner_or_reviewer.has_object_permission(self.request, self,
                                                    app):
             return app
         data = {}
         for key in ('name', 'support_email', 'support_url'):
             value = getattr(app, key)
             data[key] = unicode(value) if value else ''
         data['reason'] = 'Not available in your region.'
         raise HttpLegallyUnavailable(data)
     self.check_object_permissions(self.request, app)
     return app
Exemple #8
0
class CollectionImageViewSet(CORSMixin, SlugOrIdMixin, MarketplaceView,
                             generics.GenericAPIView, viewsets.ViewSet):
    permission_classes = [
        AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate'))
    ]
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    cors_allowed_methods = ('get', 'put', 'delete')

    hash_field = 'image_hash'
    image_suffix = ''

    # Dummy serializer to keep DRF happy when it's answering to OPTIONS.
    serializer_class = Serializer

    def perform_content_negotiation(self, request, force=False):
        """
        Force DRF's content negociation to not raise an error - It wants to use
        the format passed to the URL, but we don't care since we only deal with
        "raw" content: we don't even use the renderers.
        """
        return super(CollectionImageViewSet,
                     self).perform_content_negotiation(request, force=True)

    @cache_control(max_age=60 * 60 * 24 * 365)
    def retrieve(self, request, *args, **kwargs):
        obj = self.get_object()
        if not getattr(obj, 'image_hash', None):
            raise Http404
        return HttpResponseSendFile(request,
                                    obj.image_path(self.image_suffix),
                                    content_type='image/png')

    def update(self, request, *args, **kwargs):
        obj = self.get_object()
        try:
            img, hash_ = DataURLImageField().from_native(request.read())
        except ValidationError:
            return Response(status=status.HTTP_400_BAD_REQUEST)
        i = Image.open(img)
        with public_storage.open(obj.image_path(self.image_suffix), 'wb') as f:
            i.save(f, 'png')
        # Store the hash of the original image data sent.
        obj.update(**{self.hash_field: hash_})

        pngcrush_image.delay(obj.image_path(self.image_suffix))
        return Response(status=status.HTTP_204_NO_CONTENT)

    def destroy(self, request, *args, **kwargs):
        obj = self.get_object()
        if getattr(obj, 'image_hash', None):
            public_storage.delete(obj.image_path(self.image_suffix))
            obj.update(**{self.hash_field: None})
        return Response(status=status.HTTP_204_NO_CONTENT)
Exemple #9
0
class FeedAppViewSet(CORSMixin, viewsets.ModelViewSet):
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    permission_classes = [
        AnyOf(AllowReadOnly, GroupPermission('Feed', 'Curate'))
    ]
    queryset = FeedApp.objects.all()
    cors_allowed_methods = ('get', 'post')
    serializer_class = FeedAppSerializer
Exemple #10
0
class VersionViewSet(CORSMixin, mixins.RetrieveModelMixin,
                     mixins.UpdateModelMixin, viewsets.GenericViewSet):
    queryset = Version.objects.exclude(addon__status=mkt.STATUS_DELETED)
    serializer_class = VersionSerializer
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    permission_classes = [
        AnyOf(AllowReadOnlyIfPublic, AllowRelatedAppOwner,
              GroupPermission('Apps', 'Review'), GroupPermission('Admin', '%'))
    ]
    cors_allowed_methods = ['get', 'patch', 'put']

    def update(self, request, *args, **kwargs):
        """
        Allow a version's features to be updated.
        """
        obj = self.get_object()

        # Update features if they are provided.
        if 'features' in request.DATA:

            # Raise an exception if any invalid features are passed.
            invalid = [
                f for f in request.DATA['features']
                if f.upper() not in APP_FEATURES.keys()
            ]
            if any(invalid):
                raise ParseError('Invalid feature(s): %s' % ', '.join(invalid))

            # Update the value of each feature (note: a feature not present in
            # the form data is assumed to be False)
            data = {}
            for key, name in APP_FEATURES.items():
                field_name = 'has_' + key.lower()
                data[field_name] = key.lower() in request.DATA['features']
            obj.features.update(**data)

            del request.DATA['features']

        return super(VersionViewSet, self).update(request, *args, **kwargs)

    @action(methods=['PATCH'],
            cors_allowed_methods=VersionStatusViewSet.cors_allowed_methods)
    def status(self, request, *args, **kwargs):
        self.queryset = Version.with_deleted.all()
        kwargs['version'] = self.get_object()
        view = VersionStatusViewSet.as_view({'patch': 'update'})
        return view(request, *args, **kwargs)
Exemple #11
0
class FeedItemViewSet(CORSMixin, viewsets.ModelViewSet):
    """
    A viewset for the FeedItem class, which wraps all items that live on the
    feed.
    """
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]
    permission_classes = [AnyOf(AllowReadOnly,
                                GroupPermission('Feed', 'Curate'))]
    filter_backends = (OrderingFilter, RegionCarrierFilter)
    queryset = FeedItem.objects.no_cache().all()
    cors_allowed_methods = ('get', 'delete', 'post', 'put', 'patch')
    serializer_class = FeedItemSerializer
Exemple #12
0
class LangPackViewSet(CORSMixin, MarketplaceView, viewsets.ModelViewSet):
    model = LangPack
    cors_allowed_methods = ('get', 'post', 'put', 'patch', 'delete')
    permission_classes = [AnyOf(AllowReadOnlyIfPublic,
                                GroupPermission('LangPacks', '%'))]
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]
    serializer_class = LangPackSerializer

    def filter_queryset(self, qs):
        """Filter GET requests with active=True, unless we have the permissions
        to do differently."""
        if self.request.method == 'GET':
            active_parameter = self.request.GET.get('active')
            fxos_version_parameter = self.request.GET.get('fxos_version')

            if 'pk' in self.kwargs:
                # No filtering at all if we're trying to see a detail page: the
                # permission_classes mechanism will handle the rest for us. It
                # reveals the existence of the langpack but that's OK.
                return qs

            # Handle 'active' filtering.
            if active_parameter in ('null', 'false'):
                if action_allowed(self.request, 'LangPacks', '%'):
                    # If active=null, we don't need to filter at all (we show
                    # all langpacks regardless of their 'active' flag value).
                    # If it's false, we only show inactive langpacks.
                    if active_parameter == 'false':
                        qs = qs.filter(active=False)
                else:
                    # We don't have the permission, but the parameter to filter
                    # was passed, return a permission denied, someone is trying
                    # to see things he shouldn't be able to see.
                    self.permission_denied(self.request)
            else:
                qs = qs.filter(active=True)

            # Handle 'fxos_version' filtering if necessary.
            if fxos_version_parameter:
                qs = qs.filter(fxos_version=fxos_version_parameter)
        return qs

    def get_serializer_class(self):
        if self.request.method in ('POST', 'PUT'):
            # When using POST or PUT, we are uploading a new package.
            return LangPackUploadSerializer
        else:
            return LangPackSerializer
Exemple #13
0
class FeedShelfViewSet(BaseFeedCollectionViewSet):
    """
    A viewset for the FeedShelf class.
    """
    queryset = FeedShelf.objects.all()
    serializer_class = FeedShelfSerializer
    permission_classes = [AnyOf(OperatorShelfAuthorization,
                                *BaseFeedCollectionViewSet.permission_classes)]

    image_fields = (
        ('background_image_upload_url', 'image_hash', ''),
        ('background_image_landing_upload_url', 'image_landing_hash',
         '_landing'),
    )
Exemple #14
0
class StatusViewSet(mixins.RetrieveModelMixin, mixins.UpdateModelMixin,
                    GenericViewSet):
    queryset = Webapp.objects.all()
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication
    ]
    permission_classes = [AnyOf(AllowAppOwner, GroupPermission('Admin', '%s'))]
    serializer_class = AppStatusSerializer

    def update(self, request, *args, **kwargs):
        # PUT is disallowed, only PATCH is accepted for this endpoint.
        if request.method == 'PUT':
            raise MethodNotAllowed('PUT')
        return super(StatusViewSet, self).update(request, *args, **kwargs)
Exemple #15
0
class PrivacyPolicyViewSet(CORSMixin, SlugOrIdMixin, viewsets.GenericViewSet):
    queryset = Webapp.objects.all()
    cors_allowed_methods = ('get', )
    permission_classes = [AnyOf(AllowAppOwner, PublicAppReadOnly)]
    slug_field = 'app_slug'
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]

    def retrieve(self, request, *args, **kwargs):
        app = self.get_object()
        return response.Response(
            {'privacy_policy': unicode(app.privacy_policy)},
            content_type='application/json')
Exemple #16
0
class CreateAdditionalReviewViewSet(CreateAPIView):
    """
    API ViewSet for requesting an additional review.
    """

    model = AdditionalReview
    serializer_class = AdditionalReviewSerializer
    authentication_classes = (RestOAuthAuthentication,
                              RestSharedSecretAuthentication)
    # TODO: Change this when there is more than just the Tarako queue.
    permission_classes = [AnyOf(AppOwnerPermission,
                                GroupPermission('Apps', 'Edit'))]

    def app(self, app_id):
        self.app = Webapp.objects.get(pk=app_id)
        return self.app
Exemple #17
0
class AppTagViewSet(CORSMixin, SlugOrIdMixin, MarketplaceView,
                    viewsets.GenericViewSet):
    queryset = Webapp.objects.all()
    cors_allowed_methods = ('delete', )
    permission_classes = [
        AnyOf(AllowAppOwner, GroupPermission('Apps', 'Edit'))
    ]
    slug_field = 'app_slug'
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]

    def destroy(self, request, pk, tag_text, **kwargs):
        if tag_text == 'tarako':
            app = self.get_object()
            Tag(tag_text=tag_text).remove_tag(app)
            return response.Response(status=status.HTTP_204_NO_CONTENT)
        else:
            return response.Response(status=status.HTTP_403_FORBIDDEN)
Exemple #18
0
class VersionViewSet(CORSMixin, mixins.RetrieveModelMixin,
                     mixins.UpdateModelMixin, viewsets.GenericViewSet):
    queryset = Version.objects.filter(addon__type=amo.ADDON_WEBAPP).exclude(
        addon__status=amo.STATUS_DELETED)
    serializer_class = VersionSerializer
    authorization_classes = []
    permission_classes = [
        AnyOf(AllowRelatedAppOwner, GroupPermission('Apps', 'Review'),
              AllowReadOnlyIfPublic)
    ]
    cors_allowed_methods = ['get', 'patch', 'put']

    def update(self, request, *args, **kwargs):
        """
        Allow a version's features to be updated.
        """
        obj = self.get_object()

        # Update features if they are provided.
        if 'features' in request.DATA:

            # Raise an exception if any invalid features are passed.
            invalid = [
                f for f in request.DATA['features']
                if f.upper() not in APP_FEATURES.keys()
            ]
            if any(invalid):
                raise ParseError('Invalid feature(s): %s' % ', '.join(invalid))

            # Update the value of each feature (note: a feature not present in
            # the form data is assumed to be False)
            data = {}
            for key, name in APP_FEATURES.items():
                field_name = 'has_' + key.lower()
                data[field_name] = key.lower() in request.DATA['features']
            obj.features.update(**data)

            del request.DATA['features']

        return super(VersionViewSet, self).update(request, *args, **kwargs)
Exemple #19
0
class NoRegionSearchView(SearchView):
    """
    A search view that allows searching for public apps regardless of region
    exclusions, protected behind a permission class.

    A special class is needed because when RegionFilter is included, as it is
    in the default SearchView, it will always use whatever region was set on
    the request, and we default to setting restofworld when no region is
    passed.

    """
    authentication_classes = [
        RestSharedSecretAuthentication, RestOAuthAuthentication
    ]
    permission_classes = [
        AnyOf(GroupPermission('Feed', 'Curate'),
              GroupPermission('OperatorDashboard', '*'), IsOperatorPermission)
    ]
    filter_backends = [
        SearchQueryFilter, PublicSearchFormFilter, PublicAppsFilter,
        DeviceTypeFilter, ProfileFilter, SortingFilter
    ]
Exemple #20
0
class AppStats(CORSMixin, SlugOrIdMixin, ListAPIView):
    authentication_classes = (RestOAuthAuthentication,
                              RestSharedSecretAuthentication)
    cors_allowed_methods = ['get']
    permission_classes = [
        AnyOf(AllowAppOwner, GroupPermission('Stats', 'View'))
    ]
    queryset = Webapp.objects.all()
    slug_field = 'app_slug'

    def get(self, request, pk, metric):
        if metric not in APP_STATS:
            raise http.Http404('No metric by that name.')

        app = self.get_object()

        stat = APP_STATS[metric]

        # Perform form validation.
        form = StatsForm(request.GET)
        if not form.is_valid():
            raise ParseError(dict(form.errors.items()))

        qs = form.cleaned_data

        dimensions = {'app-id': app.id}

        if 'dimensions' in stat:
            for key, default in stat['dimensions'].items():
                val = request.GET.get(key, default)
                if val is not None:
                    # Avoid passing kwargs to the monolith client when the
                    # dimension is None to avoid facet filters being applied.
                    dimensions[key] = request.GET.get(key, default)

        return Response(
            _get_monolith_data(stat, qs.get('start'), qs.get('end'),
                               qs.get('interval'), dimensions))
Exemple #21
0
class AppViewSet(CORSMixin, SlugOrIdMixin, MarketplaceView,
                 viewsets.ModelViewSet):
    serializer_class = AppSerializer
    slug_field = 'app_slug'
    cors_allowed_methods = ('get', 'put', 'post', 'delete')
    permission_classes = [AnyOf(AllowAppOwner, AllowReviewerReadOnly,
                                AllowReadOnlyIfPublic)]
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]

    def get_queryset(self):
        return Webapp.objects.all().exclude(
            id__in=get_excluded_in(get_region().id))

    def get_base_queryset(self):
        return Webapp.objects.all()

    def get_object(self, queryset=None):
        try:
            app = super(AppViewSet, self).get_object()
        except Http404:
            app = super(AppViewSet, self).get_object(self.get_base_queryset())
            # Owners and reviewers can see apps regardless of region.
            owner_or_reviewer = AnyOf(AllowAppOwner, AllowReviewerReadOnly)
            if owner_or_reviewer.has_object_permission(self.request, self,
                                                       app):
                return app
            data = {}
            for key in ('name', 'support_email', 'support_url'):
                value = getattr(app, key)
                data[key] = unicode(value) if value else ''
            data['reason'] = 'Not available in your region.'
            raise HttpLegallyUnavailable(data)
        self.check_object_permissions(self.request, app)
        return app

    def create(self, request, *args, **kwargs):
        uuid = request.DATA.get('upload', '')
        if uuid:
            is_packaged = True
        else:
            uuid = request.DATA.get('manifest', '')
            is_packaged = False
        if not uuid:
            raise serializers.ValidationError(
                'No upload or manifest specified.')

        try:
            upload = FileUpload.objects.get(uuid=uuid)
        except FileUpload.DoesNotExist:
            raise exceptions.ParseError('No upload found.')
        if not upload.valid:
            raise exceptions.ParseError('Upload not valid.')

        if not request.amo_user.read_dev_agreement:
            log.info(u'Attempt to use API without dev agreement: %s'
                     % request.amo_user.pk)
            raise exceptions.PermissionDenied('Terms of Service not accepted.')
        if not (upload.user and upload.user.pk == request.amo_user.pk):
            raise exceptions.PermissionDenied('You do not own that app.')
        plats = [Platform.objects.get(id=amo.PLATFORM_ALL.id)]

        # Create app, user and fetch the icon.
        obj = Webapp.from_upload(upload, plats, is_packaged=is_packaged)
        AddonUser(addon=obj, user=request.amo_user).save()
        tasks.fetch_icon.delay(obj)
        record_action('app-submitted', request, {'app-id': obj.pk})

        log.info('App created: %s' % obj.pk)
        data = AppSerializer(
            context=self.get_serializer_context()).to_native(obj)

        return response.Response(
            data, status=201,
            headers={'Location': reverse('app-detail', kwargs={'pk': obj.pk})})

    def update(self, request, *args, **kwargs):
        # Fail if the app doesn't exist yet.
        self.get_object()
        r = super(AppViewSet, self).update(request, *args, **kwargs)
        # Be compatible with tastypie responses.
        if r.status_code == 200:
            r.status_code = 202
        return r

    def list(self, request, *args, **kwargs):
        if not request.amo_user:
            log.info('Anonymous listing not allowed')
            raise exceptions.PermissionDenied('Anonymous listing not allowed.')

        self.object_list = self.filter_queryset(self.get_queryset().filter(
            authors=request.amo_user))
        page = self.paginate_queryset(self.object_list)
        serializer = self.get_pagination_serializer(page)
        return response.Response(serializer.data)

    def partial_update(self, request, *args, **kwargs):
        raise exceptions.MethodNotAllowed('PATCH')

    @action()
    def content_ratings(self, request, *args, **kwargs):
        app = self.get_object()
        form = IARCGetAppInfoForm(data=request.DATA, app=app)

        if form.is_valid():
            try:
                form.save(app)
                return Response(status=status.HTTP_201_CREATED)
            except django_forms.ValidationError:
                pass

        return Response(form.errors, status=status.HTTP_400_BAD_REQUEST)

    @action(methods=['POST'],
            cors_allowed_methods=PreviewViewSet.cors_allowed_methods)
    def preview(self, request, *args, **kwargs):
        kwargs['app'] = self.get_object()
        view = PreviewViewSet.as_view({'post': '_create'})
        return view(request, *args, **kwargs)

    @action(methods=['PUT'], cors_allowed_methods=['put'])
    def icon(self, request, *args, **kwargs):
        app = self.get_object()

        data_form = IconJSONForm(request.DATA)
        if not data_form.is_valid():
            return Response(data_form.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        form = AppFormMedia(data_form.cleaned_data, request=request)
        if not form.is_valid():
            return Response(data_form.errors,
                            status=status.HTTP_400_BAD_REQUEST)

        form.save(app)
        return Response(status=status.HTTP_200_OK)
Exemple #22
0
class AppStatsTotal(CORSMixin, SlugOrIdMixin, ListAPIView):
    authentication_classes = (RestOAuthAuthentication,
                              RestSharedSecretAuthentication)
    cors_allowed_methods = ['get']
    permission_classes = [
        AnyOf(AllowAppOwner, GroupPermission('Stats', 'View'))
    ]
    queryset = Webapp.objects.all()
    slug_field = 'app_slug'

    def get(self, request, pk):
        app = self.get_object()

        try:
            client = get_monolith_client()
        except requests.ConnectionError as e:
            log.info('Monolith connection error: {0}'.format(e))
            raise ServiceUnavailable

        # Note: We have to do this as separate requests so that if one fails
        # the rest can still be returned.
        data = {}
        for metric, stat in APP_STATS_TOTAL.items():
            data[metric] = {}
            query = {
                'query': {
                    'match_all': {}
                },
                'facets': {
                    metric: {
                        'statistical': {
                            'field': stat['metric']
                        },
                        'facet_filter': {
                            'term': {
                                'app-id': app.id
                            }
                        }
                    }
                },
                'size': 0
            }

            try:
                resp = client.raw(query)
            except ValueError as e:
                log.info('Received value error from monolith client: %s' % e)
                continue

            for metric, facet in resp.get('facets', {}).items():
                count = facet.get('count', 0)

                # We filter out facets with count=0 to avoid returning things
                # like `'max': u'-Infinity'`.
                if count > 0:
                    for field in ('max', 'mean', 'min', 'std_deviation',
                                  'sum_of_squares', 'total', 'variance'):
                        value = facet.get(field)
                        if value is not None:
                            data[metric][field] = value

        return Response(data)
Exemple #23
0
class RatingViewSet(CORSMixin, MarketplaceView, ModelViewSet):
    # Unfortunately, the model class name for ratings is "Review".
    queryset = Review.objects.valid()
    cors_allowed_methods = ('get', 'post', 'put', 'delete')
    permission_classes = [
        ByHttpMethod({
            'options':
            AllowAny,  # Needed for CORS.
            'get':
            AllowAny,
            'post':
            IsAuthenticated,
            'put':
            AnyOf(AllowOwner, GroupPermission('Addons', 'Edit')),
            'delete':
            AnyOf(AllowOwner, AllowRelatedAppOwner,
                  GroupPermission('Users', 'Edit'),
                  GroupPermission('Addons', 'Edit')),
        })
    ]
    authentication_classes = [
        RestOAuthAuthentication, RestSharedSecretAuthentication,
        RestAnonymousAuthentication
    ]
    serializer_class = RatingSerializer
    paginator_class = RatingPaginator

    # FIXME: Add throttling ? Original tastypie version didn't have it...

    def get_queryset(self):
        qs = super(RatingViewSet, self).get_queryset()
        # Mature regions show only reviews from within that region.
        # FIXME: what is client_data, how is it filled ? There was no tests
        # for this.
        if not self.request.REGION.adolescent:
            qs = qs.filter(client_data__region=self.request.REGION.id)
        return qs

    def filter_queryset(self, queryset):
        """
        Custom filter method allowing us to filter on app slug/pk and user pk
        (or the special user value "mine"). A full FilterSet is overkill here.
        """
        filters = {}
        app = self.request.GET.get('app')
        user = self.request.GET.get('user')
        if app:
            self.app = self.get_app(app)
            filters['addon'] = self.app
        if user:
            filters['user'] = self.get_user(user)

        if filters:
            queryset = queryset.filter(**filters)
        return queryset

    def get_user(self, ident):
        pk = ident
        if pk == 'mine':
            user = amo.get_user()
            if not user:
                # You must be logged in to use "mine".
                raise NotAuthenticated()
            pk = user.pk
        return pk

    def get_app(self, ident):
        try:
            app = Webapp.objects.by_identifier(ident)
        except Webapp.DoesNotExist:
            raise Http404
        current_region = get_region()
        if ((not app.is_public() or not app.listed_in(region=current_region))
                and not check_addon_ownership(self.request, app)):
            # App owners and admin can see the app even if it's not public
            # or not available in the current region. Regular users or
            # anonymous users can't.
            raise PermissionDenied('The app requested is not public or not '
                                   'available in region "%s".' %
                                   current_region.slug)
        return app

    def list(self, request, *args, **kwargs):
        response = super(RatingViewSet, self).list(request, *args, **kwargs)
        app = getattr(self, 'app', None)
        if app:
            user, info = self.get_extra_data(app, request.amo_user)
            response.data['user'] = user
            response.data['info'] = info
        return response

    def destroy(self, request, *args, **kwargs):
        obj = self.get_object()
        amo.log(amo.LOG.DELETE_REVIEW, obj.addon, obj)
        log.debug('[Review:%s] Deleted by %s' %
                  (obj.pk, self.request.amo_user.id))
        return super(RatingViewSet, self).destroy(request, *args, **kwargs)

    def post_save(self, obj, created=False):
        app = obj.addon
        if created:
            amo.log(amo.LOG.ADD_REVIEW, app, obj)
            log.debug('[Review:%s] Created by user %s ' %
                      (obj.pk, self.request.amo_user.id))
            record_action('new-review', self.request, {'app-id': app.id})
        else:
            amo.log(amo.LOG.EDIT_REVIEW, app, obj)
            log.debug('[Review:%s] Edited by %s' %
                      (obj.pk, self.request.amo_user.id))

    def partial_update(self, *args, **kwargs):
        # We don't need/want PATCH for now.
        raise MethodNotAllowed('PATCH is not supported for this endpoint.')

    def get_extra_data(self, app, amo_user):
        extra_user = None

        if amo_user and not amo_user.is_anonymous():
            if app.is_premium():
                # If the app is premium, you need to purchase it to rate it.
                can_rate = app.has_purchased(amo_user)
            else:
                # If the app is free, you can not be one of the authors.
                can_rate = not app.has_author(amo_user)

            filters = {'addon': app, 'user': amo_user}
            if app.is_packaged:
                filters['version'] = app.current_version

            extra_user = {
                'can_rate': can_rate,
                'has_rated': Review.objects.valid().filter(**filters).exists()
            }

        extra_info = {
            'average': app.average_rating,
            'slug': app.app_slug,
            'current_version': getattr(app.current_version, 'version', None)
        }

        return extra_user, extra_info

    @action(methods=['POST'], permission_classes=[AllowAny])
    def flag(self, request, pk=None):
        self.kwargs[self.lookup_field] = pk
        self.get_object()  # Will check that the Review instance is valid.
        request._request.CORS = RatingFlagViewSet.cors_allowed_methods
        view = RatingFlagViewSet.as_view({'post': 'create'})
        return view(request, *self.args, **{'review': pk})
 def test_has_object_permission_partial_fail(self):
     request = RequestFactory().get('/')
     ok_(not AnyOf(FailPartialPermission, PartialFailPermission)
         ().has_object_permission(request, 'myview', None))
 def test_has_permission_fail(self):
     request = RequestFactory().get('/')
     ok_(not AnyOf(AllowNone, AllowNone)
         ().has_permission(request, 'myview'))
Exemple #26
0
class RatingViewSet(CORSMixin, MarketplaceView, ModelViewSet):
    # Unfortunately, the model class name for ratings is "Review".
    # We prefetch 'version' because it's often going to be similar, and select
    # related 'user' to avoid extra queries.
    queryset = (Review.objects.valid()
                .prefetch_related('version')
                .select_related('user'))
    cors_allowed_methods = ('get', 'post', 'put', 'delete')
    permission_classes = [ByHttpMethod({
        'options': AllowAny,  # Needed for CORS.
        'get': AllowAny,
        'head': AllowAny,
        'post': IsAuthenticated,
        'put': AnyOf(AllowOwner,
                     GroupPermission('Apps', 'Edit')),
        'delete': AnyOf(AllowOwner,
                        AllowRelatedAppOwner,
                        GroupPermission('Users', 'Edit'),
                        GroupPermission('Apps', 'Edit')),
    })]
    authentication_classes = [RestOAuthAuthentication,
                              RestSharedSecretAuthentication,
                              RestAnonymousAuthentication]
    serializer_class = RatingSerializer

    def paginator_class(self, *args, **kwargs):
        paginator = super(RatingViewSet, self).paginator_class(*args, **kwargs)
        if hasattr(self, 'app'):
            # If an app is passed, we want the paginator count to match the
            # number of reviews on the app, without doing an extra query.
            paginator._count = self.app.total_reviews
        return paginator

    # FIXME: Add throttling ? Original tastypie version didn't have it...

    def filter_queryset(self, queryset):
        """
        Custom filter method allowing us to filter on app slug/pk and user pk
        (or the special user value "mine"). A full FilterSet is overkill here.
        """
        filters = Q()
        app = self.request.GET.get('app')
        user = self.request.GET.get('user')
        lang = self.request.GET.get('lang')
        match_lang = self.request.GET.get('match_lang')
        if app:
            self.app = self.get_app(app)
            filters &= Q(addon=self.app)
        if user:
            filters &= Q(user=self.get_user(user))
        elif lang and match_lang == '1':
            filters &= Q(lang=lang)

        if filters:
            queryset = queryset.filter(filters)
        return queryset

    def get_user(self, ident):
        pk = ident
        if pk == 'mine':
            user = mkt.get_user()
            if not user or not user.is_authenticated():
                # You must be logged in to use "mine".
                raise NotAuthenticated()
            pk = user.pk
        return pk

    def get_app(self, ident):
        try:
            app = Webapp.objects.by_identifier(ident)
        except Webapp.DoesNotExist:
            raise Http404
        current_region = get_region()
        if ((not app.is_public() or
             not app.listed_in(region=current_region)) and
                not check_addon_ownership(self.request, app)):
            # App owners and admin can see the app even if it's not public
            # or not available in the current region. Regular users or
            # anonymous users can't.
            raise PermissionDenied(
                'The app requested is not public or not available in region '
                '"%s".' % current_region.slug)
        return app

    def list(self, request, *args, **kwargs):
        response = super(RatingViewSet, self).list(request, *args, **kwargs)
        app = getattr(self, 'app', None)
        if app:
            user, info = self.get_extra_data(app, request.user)
            response.data['user'] = user
            response.data['info'] = info
        return response

    def destroy(self, request, *args, **kwargs):
        obj = self.get_object()
        mkt.log(mkt.LOG.DELETE_REVIEW, obj.addon, obj,
                details=dict(title=unicode(obj.title),
                             body=unicode(obj.body),
                             addon_id=obj.addon.id,
                             addon_title=unicode(obj.addon.name)))
        log.debug('[Review:%s] Deleted by %s' %
                  (obj.pk, self.request.user.id))
        return super(RatingViewSet, self).destroy(request, *args, **kwargs)

    def post_save(self, obj, created=False):
        app = obj.addon
        if created:
            mkt.log(mkt.LOG.ADD_REVIEW, app, obj)
            log.debug('[Review:%s] Created by user %s ' %
                      (obj.pk, self.request.user.id))
            record_action('new-review', self.request, {'app-id': app.id})
        else:
            mkt.log(mkt.LOG.EDIT_REVIEW, app, obj)
            log.debug('[Review:%s] Edited by %s' %
                      (obj.pk, self.request.user.id))

    def partial_update(self, *args, **kwargs):
        # We don't need/want PATCH for now.
        raise MethodNotAllowed('PATCH is not supported for this endpoint.')

    def get_extra_data(self, app, user):
        extra_user = None

        if user.is_authenticated():
            if app.is_premium():
                # If the app is premium, you need to purchase it to rate it.
                can_rate = app.has_purchased(user)
            else:
                # If the app is free, you can not be one of the authors.
                can_rate = not app.has_author(user)

            filters = {
                'addon': app,
                'user': user
            }
            if app.is_packaged:
                filters['version'] = app.current_version

            extra_user = {
                'can_rate': can_rate,
                'has_rated': Review.objects.valid().filter(**filters).exists()
            }

        extra_info = {
            'average': app.average_rating,
            'slug': app.app_slug,
            'total_reviews': app.total_reviews,
            'current_version': getattr(app.current_version, 'version', None)
        }

        return extra_user, extra_info

    @action(methods=['POST'], permission_classes=[AllowAny])
    def flag(self, request, pk=None):
        self.kwargs[self.lookup_field] = pk
        self.get_object()  # Will check that the Review instance is valid.
        request._request.CORS = RatingFlagViewSet.cors_allowed_methods
        view = RatingFlagViewSet.as_view({'post': 'create'})
        return view(request, *self.args, **{'review': pk})