예제 #1
0
class AddonReviewerViewSet(GenericViewSet):
    log = olympia.core.logger.getLogger('z.reviewers')

    @detail_route(methods=['post'],
                  permission_classes=[AllowAnyKindOfReviewer])
    def subscribe(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        ReviewerSubscription.objects.get_or_create(user=request.user,
                                                   addon=addon)
        return Response(status=status.HTTP_202_ACCEPTED)

    @detail_route(methods=['post'],
                  permission_classes=[AllowAnyKindOfReviewer])
    def unsubscribe(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        ReviewerSubscription.objects.filter(user=request.user,
                                            addon=addon).delete()
        return Response(status=status.HTTP_202_ACCEPTED)

    @detail_route(
        methods=['post'],
        permission_classes=[GroupPermission(amo.permissions.REVIEWS_ADMIN)])
    def disable(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        ActivityLog.create(amo.LOG.CHANGE_STATUS, addon, amo.STATUS_DISABLED)
        self.log.info('Addon "%s" status changed to: %s', addon.slug,
                      amo.STATUS_DISABLED)
        addon.update(status=amo.STATUS_DISABLED)
        addon.update_version()
        return Response(status=status.HTTP_202_ACCEPTED)

    @detail_route(
        methods=['post'],
        permission_classes=[GroupPermission(amo.permissions.REVIEWS_ADMIN)])
    def enable(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        ActivityLog.create(amo.LOG.CHANGE_STATUS, addon, amo.STATUS_PUBLIC)
        self.log.info('Addon "%s" status changed to: %s', addon.slug,
                      amo.STATUS_PUBLIC)
        addon.update(status=amo.STATUS_PUBLIC)
        # Call update_status() to fix the status if the add-on is not actually
        # in a state that allows it to be public.
        addon.update_status()
        return Response(status=status.HTTP_202_ACCEPTED)

    @detail_route(
        methods=['patch'],
        permission_classes=[GroupPermission(amo.permissions.REVIEWS_ADMIN)])
    def flags(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        instance, _ = AddonReviewerFlags.objects.get_or_create(addon=addon)
        serializer = AddonReviewerFlagsSerializer(instance,
                                                  data=request.data,
                                                  partial=True)
        serializer.is_valid(raise_exception=True)
        # If pending info request was modified, log it.
        if 'pending_info_request' in serializer.initial_data:
            ActivityLog.create(amo.LOG.ADMIN_ALTER_INFO_REQUEST, addon)
        serializer.save()
        return Response(serializer.data)
예제 #2
0
class AddonReviewerViewSet(GenericViewSet):
    log = olympia.core.logger.getLogger('z.reviewers')

    @action(detail=True,
            methods=['post'],
            permission_classes=[AllowAnyKindOfReviewer])
    def subscribe(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        ReviewerSubscription.objects.get_or_create(user=request.user,
                                                   addon=addon)
        return Response(status=status.HTTP_202_ACCEPTED)

    @action(detail=True,
            methods=['post'],
            permission_classes=[AllowAnyKindOfReviewer])
    def unsubscribe(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        ReviewerSubscription.objects.filter(user=request.user,
                                            addon=addon).delete()
        return Response(status=status.HTTP_202_ACCEPTED)

    @action(
        detail=True,
        methods=['post'],
        permission_classes=[GroupPermission(amo.permissions.REVIEWS_ADMIN)])
    def disable(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        addon.force_disable()
        return Response(status=status.HTTP_202_ACCEPTED)

    @action(
        detail=True,
        methods=['post'],
        permission_classes=[GroupPermission(amo.permissions.REVIEWS_ADMIN)])
    def enable(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        addon.force_enable()
        return Response(status=status.HTTP_202_ACCEPTED)

    @action(
        detail=True,
        methods=['patch'],
        permission_classes=[GroupPermission(amo.permissions.REVIEWS_ADMIN)])
    def flags(self, request, **kwargs):
        addon = get_object_or_404(Addon, pk=kwargs['pk'])
        instance, _ = AddonReviewerFlags.objects.get_or_create(addon=addon)
        serializer = AddonReviewerFlagsSerializer(instance,
                                                  data=request.data,
                                                  partial=True)
        serializer.is_valid(raise_exception=True)
        # If pending info request was modified, log it.
        if 'pending_info_request' in serializer.initial_data:
            ActivityLog.create(amo.LOG.ADMIN_ALTER_INFO_REQUEST, addon)
        serializer.save()
        return Response(serializer.data)
예제 #3
0
class InternalAddonViewSet(AddonViewSet):
    # Restricted to specific permissions.
    permission_classes = [
        AnyOf(GroupPermission(amo.permissions.ADMIN_TOOLS_VIEW),
              GroupPermission(amo.permissions.REVIEWER_ADMIN_TOOLS_VIEW))
    ]

    # Internal tools allow access to everything, including deleted add-ons.
    queryset = Addon.unfiltered.all()

    # Can display unlisted data.
    serializer_class = AddonSerializerWithUnlistedData
예제 #4
0
class InternalAddonViewSet(AddonViewSet):
    # Restricted to specific permissions.
    permission_classes = [
        AnyOf(GroupPermission('AdminTools', 'View'),
              GroupPermission('ReviewerAdminTools', 'View'))
    ]

    # Internal tools allow access to everything, including deleted add-ons.
    queryset = Addon.unfiltered.all()

    # Can display unlisted data.
    serializer_class = AddonSerializerWithUnlistedData
예제 #5
0
class InternalAddonSearchView(AddonSearchView):
    # AddonSearchView disables auth classes so we need to add it back.
    authentication_classes = [JSONWebTokenAuthentication]

    # Similar to AddonSearchView but without the PublicContentFilter.
    filter_backends = [SearchQueryFilter, SortingFilter]

    # Restricted to specific permissions.
    permission_classes = [
        AnyOf(GroupPermission('AdminTools', 'View'),
              GroupPermission('ReviewerAdminTools', 'View'))
    ]
예제 #6
0
class CollectionViewSet(ModelViewSet):
    permission_classes = [
        AnyOf(
            # Collection authors can do everything.
            AllowCollectionAuthor,
            # Admins can do everything except create.
            AllOf(GroupPermission(amo.permissions.COLLECTIONS_EDIT),
                  PreventActionPermission('create')),
            # Everyone else can do read-only stuff, except list.
            AllOf(AllowReadOnlyIfPublic, PreventActionPermission('list'))),
    ]
    serializer_class = CollectionSerializer
    lookup_field = 'slug'

    def get_account_viewset(self):
        if not hasattr(self, 'account_viewset'):
            self.account_viewset = AccountViewSet(
                request=self.request,
                permission_classes=[],  # We handled permissions already.
                kwargs={'pk': self.kwargs['user_pk']})
        return self.account_viewset

    def get_queryset(self):
        return Collection.objects.filter(
            author=self.get_account_viewset().get_object())
예제 #7
0
class CollectionViewSet(ModelViewSet):
    permission_classes = [
        AnyOf(
            # Collection authors can do everything.
            AllowCollectionAuthor,
            # Collection contributors can access an existing collection, and
            # change it's addons, but can't delete or edit it's details.
            AllOf(
                AllowCollectionContributor,
                PreventActionPermission(
                    ['create', 'list', 'update', 'destroy',
                     'partial_update'])),
            # Admins can do everything except create.
            AllOf(GroupPermission(amo.permissions.COLLECTIONS_EDIT),
                  PreventActionPermission('create')),
            # Everyone else can do read-only stuff, except list.
            AllOf(AllowReadOnlyIfPublic, PreventActionPermission('list'))),
    ]
    serializer_class = CollectionSerializer
    lookup_field = 'slug'

    def get_account_viewset(self):
        if not hasattr(self, 'account_viewset'):
            self.account_viewset = AccountViewSet(
                request=self.request,
                permission_classes=[],  # We handled permissions already.
                kwargs={'pk': self.kwargs['user_pk']})
        return self.account_viewset

    def get_queryset(self):
        return Collection.objects.filter(author=self.get_account_viewset().
                                         get_object()).order_by('-modified')
예제 #8
0
class AccountSuperCreate(APIView):
    authentication_classes = [JWTKeyAuthentication]
    permission_classes = [
        IsAuthenticated,
        GroupPermission(amo.permissions.ACCOUNTS_SUPER_CREATE)
    ]

    @waffle_switch('super-create-accounts')
    def post(self, request):
        serializer = AccountSuperCreateSerializer(data=request.data)
        if not serializer.is_valid():
            return Response({'errors': serializer.errors}, status=422)

        data = serializer.data

        group = serializer.validated_data.get('group', None)
        user_token = os.urandom(4).encode('hex')
        username = data.get('username', 'super-created-{}'.format(user_token))
        fxa_id = data.get('fxa_id', None)
        email = data.get('email', '{}@addons.mozilla.org'.format(username))

        user = UserProfile.objects.create(
            username=username,
            email=email,
            fxa_id=fxa_id,
            display_name='Super Created {}'.format(user_token),
            notes='auto-generated from API')
        user.save()

        if group:
            GroupUser.objects.create(user=user, group=group)

        login(request, user)
        request.session.save()

        log.info(u'API user {api_user} created and logged in a user from '
                 u'the super-create API: user_id: {user.pk}; '
                 u'user_name: {user.username}; fxa_id: {user.fxa_id}; '
                 u'group: {group}'.format(user=user,
                                          api_user=request.user,
                                          group=group))

        cookie = {
            'name': settings.SESSION_COOKIE_NAME,
            'value': request.session.session_key,
        }
        cookie['encoded'] = '{name}={value}'.format(**cookie)

        return Response(
            {
                'user_id': user.pk,
                'username': user.username,
                'email': user.email,
                'display_name': user.display_name,
                'groups': list(
                    (g.pk, g.name, g.rules) for g in user.groups.all()),
                'fxa_id': user.fxa_id,
                'session_cookie': cookie,
            },
            status=201)
예제 #9
0
 def check_permissions(self, request):
     requested = self.request.GET.get('filter')
     if requested == 'all_with_deleted':
         # To see deleted versions, you need Addons:ViewDeleted.
         self.permission_classes = [
             GroupPermission(amo.permissions.ADDONS_VIEW_DELETED)
         ]
     elif requested == 'all_with_unlisted':
         # To see unlisted versions, you need to be add-on author or
         # unlisted reviewer.
         self.permission_classes = [
             AnyOf(AllowUnlistedViewerOrReviewer, AllowAddonAuthor)
         ]
     elif requested == 'all_without_unlisted':
         # To see all listed versions (not just public ones) you need to
         # be add-on author or reviewer.
         self.permission_classes = [
             AnyOf(
                 AllowListedViewerOrReviewer,
                 AllowUnlistedViewerOrReviewer,
                 AllowAddonAuthor,
             )
         ]
     # When listing, we can't use AllowRelatedObjectPermissions() with
     # check_permissions(), because AllowAddonAuthor needs an author to
     # do the actual permission check. To work around that, we call
     # super + check_object_permission() ourselves, passing down the
     # addon object directly.
     # Note that just calling get_addon_object() will trigger permission
     # checks on its own using AddonViewSet permission classes, regardless
     # of what permission classes are set on self.
     return super().check_object_permissions(request, self.get_addon_object())
예제 #10
0
class CollectionViewSet(ModelViewSet):
    permission_classes = [
        AnyOf(
            # Collection authors can do everything.
            AllowCollectionAuthor,
            # Collection contributors can access an existing collection, and
            # change it's addons, but can't delete or edit it's details.
            AllOf(
                AllowCollectionContributor,
                PreventActionPermission(
                    ['create', 'list', 'update', 'destroy',
                     'partial_update'])),
            # Admins can do everything except create.
            AllOf(GroupPermission(amo.permissions.COLLECTIONS_EDIT),
                  PreventActionPermission('create')),
            # Everyone else can do read-only stuff, except list.
            AllOf(AllowReadOnlyIfPublic, PreventActionPermission('list'))),
    ]
    lookup_url_kwarg = 'slug'

    @property
    def lookup_field(self):
        identifier = self.kwargs.get(self.lookup_url_kwarg)
        if identifier and identifier.isdigit():
            lookup_field = 'pk'
        else:
            # If the identifier is anything other than a digit, it's the slug.
            lookup_field = 'slug'
        return lookup_field

    def get_account_viewset(self):
        if not hasattr(self, 'account_viewset'):
            self.account_viewset = AccountViewSet(
                request=self.request,
                permission_classes=[],  # We handled permissions already.
                kwargs={'pk': self.kwargs['user_pk']})
        return self.account_viewset

    def get_serializer_class(self):
        with_addons = ('with_addons' in self.request.GET
                       and self.action == 'retrieve')
        return (CollectionSerializer
                if not with_addons else CollectionWithAddonsSerializer)

    def get_queryset(self):
        return Collection.objects.filter(author=self.get_account_viewset().
                                         get_object()).order_by('-modified')

    def get_addons_queryset(self):
        collection_addons_viewset = CollectionAddonViewSet(
            request=self.request)
        # Set this to avoid a pointless lookup loop.
        collection_addons_viewset.collection_viewset = self
        # This needs to be list to make the filtering work.
        collection_addons_viewset.action = 'list'
        qs = collection_addons_viewset.get_queryset()
        # Now limit and sort
        limit = settings.REST_FRAMEWORK['PAGE_SIZE']
        sort = collection_addons_viewset.ordering[0]
        return qs.order_by(sort)[:limit]
예제 #11
0
 def check_permissions(self, request):
     requested = self.request.GET.get('filter')
     if self.action == 'list':
         if requested == 'all_with_deleted':
             # To see deleted versions, you need Addons:ViewDeleted.
             self.permission_classes = [
                 GroupPermission(amo.permissions.ADDONS_VIEW_DELETED)
             ]
         elif requested == 'all_with_unlisted':
             # To see unlisted versions, you need to be add-on author or
             # unlisted reviewer.
             self.permission_classes = [
                 AnyOf(AllowReviewerUnlisted, AllowAddonAuthor)
             ]
         elif requested == 'all_without_unlisted':
             # To see all listed versions (not just public ones) you need to
             # be add-on author or reviewer.
             self.permission_classes = [
                 AnyOf(AllowReviewer, AllowReviewerUnlisted,
                       AllowAddonAuthor)
             ]
         # When listing, we can't use AllowRelatedObjectPermissions() with
         # check_permissions(), because AllowAddonAuthor needs an author to
         # do the actual permission check. To work around that, we call
         # super + check_object_permission() ourselves, passing down the
         # addon object directly.
         return super(AddonVersionViewSet, self).check_object_permissions(
             request, self.get_addon_object())
     super(AddonVersionViewSet, self).check_permissions(request)
예제 #12
0
    def check_object_permissions(self, request, obj):
        # If the instance is marked as deleted and the client is not allowed to
        # see deleted instances, we want to return a 404, behaving as if it
        # does not exist.
        if obj.deleted and not GroupPermission(
            amo.permissions.ADDONS_VIEW_DELETED
        ).has_object_permission(request, self, obj):
            raise http.Http404

        if obj.channel == amo.RELEASE_CHANNEL_UNLISTED:
            # If the instance is unlisted, only allow unlisted reviewers and
            # authors..
            self.permission_classes = [
                AllowRelatedObjectPermissions(
                    'addon', [AnyOf(AllowUnlistedViewerOrReviewer, AllowAddonAuthor)]
                )
            ]
        elif not obj.is_public():
            # If the instance is disabled, only allow reviewers and authors.
            self.permission_classes = [
                AllowRelatedObjectPermissions(
                    'addon', [AnyOf(AllowListedViewerOrReviewer, AllowAddonAuthor)]
                )
            ]
        super(AddonVersionViewSet, self).check_object_permissions(request, obj)
예제 #13
0
class AppVersionView(APIView):
    authentication_classes = [JWTKeyAuthentication]
    permission_classes = [GroupPermission(amo.permissions.APPVERSIONS_CREATE)]

    def put(self, request, *args, **kwargs):
        # For each request, we'll try to create up to 3 versions for each app,
        # one for the parameter in the URL, one for the corresponding "release"
        # version if it's different (if 79.0a1 is passed, the base would be
        # 79.0. If 79.0 is passed, then we'd skip that one as they are the
        # same) and a last one for the corresponding max version with a star
        # (if 79.0 or 79.0a1 is passed, then this would be 79.*)
        # We validate the app parameter, but always try to create the versions
        # for both Firefox and Firefox for Android anyway, because at the
        # extension manifest level there is no difference so for validation
        # purposes we want to keep both in sync.
        application = amo.APPS.get(kwargs.get('application'))
        if not application:
            raise ParseError('Invalid application parameter')
        requested_version = kwargs.get('version')
        if not requested_version or not version_re.match(requested_version):
            raise ParseError('Invalid version parameter')
        version_data = version_dict(requested_version)
        release_version = '%d.%d' % (version_data['major'], version_data['minor1'] or 0)
        star_version = '%d.*' % version_data['major']
        created_firefox = self.create_versions_for_app(
            application=amo.FIREFOX,
            requested_version=requested_version,
            release_version=release_version,
            star_version=star_version,
        )
        created_android = self.create_versions_for_app(
            application=amo.ANDROID,
            requested_version=requested_version,
            release_version=release_version,
            star_version=star_version,
        )
        created = created_firefox or created_android
        status_code = HTTP_201_CREATED if created else HTTP_202_ACCEPTED
        return Response(status=status_code)

    def create_versions_for_app(
        self, *, application, requested_version, release_version, star_version
    ):
        _, created_requested = AppVersion.objects.get_or_create(
            application=application.id, version=requested_version
        )
        if requested_version != release_version:
            _, created_release = AppVersion.objects.get_or_create(
                application=application.id, version=release_version
            )
        else:
            created_release = False
        if requested_version != star_version:
            _, created_star = AppVersion.objects.get_or_create(
                application=application.id, version=star_version
            )
        else:
            created_star = False
        return created_requested or created_release or created_star
예제 #14
0
class ProtectedView(APIView):
    # Use session auth for this test view because it's easy, and the goal is
    # to test the permission, not the authentication.
    authentication_classes = [SessionAuthentication]
    permission_classes = [GroupPermission(amo.permissions.NONE)]

    def get(self, request):
        return Response('ok')
예제 #15
0
class AccountSuperCreate(JWTProtectedView):
    permission_classes = [
        IsAuthenticated, GroupPermission('Accounts', 'SuperCreate')]

    @waffle_switch('super-create-accounts')
    def post(self, request):
        serializer = AccountSuperCreateSerializer(data=request.DATA)
        if not serializer.is_valid():
            return Response({'errors': serializer.errors},
                            status=422)

        data = serializer.data
        # In a future version of DRF this could be validated_data['group']:
        group = serializer.object.get('group')
        user_token = os.urandom(4).encode('hex')
        username = data['username'] or 'super-created-{}'.format(user_token)
        fxa_id = data['fxa_id'] or None
        email = data['email'] or '{}@addons.mozilla.org'.format(username)
        password = data['password'] or os.urandom(16).encode('hex')

        user = UserProfile.objects.create(
            username=username,
            email=email,
            fxa_id=fxa_id,
            display_name='Super Created {}'.format(user_token),
            is_verified=True,
            confirmationcode='',
            notes='auto-generated from API')
        user.set_password(password)
        user.save()

        if group:
            GroupUser.objects.create(user=user, group=group)

        login(request, user)
        request.session.save()

        log.info(u'API user {api_user} created and logged in a user from '
                 u'the super-create API: user_id: {user.pk}; '
                 u'user_name: {user.username}; fxa_id: {user.fxa_id}; '
                 u'group: {group}'
                 .format(user=user, api_user=request.user, group=group))

        cookie = {
            'name': settings.SESSION_COOKIE_NAME,
            'value': request.session.session_key,
        }
        cookie['encoded'] = '{name}={value}'.format(**cookie)

        return Response({
            'user_id': user.pk,
            'username': user.username,
            'email': user.email,
            'display_name': user.display_name,
            'groups': list((g.pk, g.name, g.rules) for g in user.groups.all()),
            'fxa_id': user.fxa_id,
            'session_cookie': cookie,
        }, status=201)
예제 #16
0
class AccountNotificationViewSet(ListModelMixin, GenericViewSet):
    """Returns account notifications.

    If not already set by the user, defaults will be returned.
    """

    permission_classes = [IsAuthenticated]
    # We're pushing the primary permission checking to AccountViewSet for ease.
    account_permission_classes = [
        AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT))
    ]
    serializer_class = UserNotificationSerializer
    paginator = None

    def get_account_viewset(self):
        if not hasattr(self, 'account_viewset'):
            self.account_viewset = AccountViewSet(
                request=self.request,
                permission_classes=self.account_permission_classes,
                kwargs={'pk': self.kwargs['user_pk']})
        return self.account_viewset

    def _get_default_object(self, notification):
        return UserNotification(user=self.get_account_viewset().get_object(),
                                notification_id=notification.id,
                                enabled=notification.default_checked)

    def get_queryset(self):
        queryset = UserNotification.objects.filter(
            user=self.get_account_viewset().get_object())
        # Put it into a dict so we can easily check for existence.
        set_notifications = {
            user_nfn.notification.short: user_nfn
            for user_nfn in queryset
        }
        out = []
        for notification in NOTIFICATIONS:
            out.append(
                set_notifications.get(
                    notification.short,  # It's been set by the user.
                    self._get_default_object(
                        notification)))  # Otherwise, default.
        return out

    def create(self, request, *args, **kwargs):
        # Loop through possible notifications.
        queryset = self.get_queryset()
        for notification in queryset:
            # Careful with ifs.  Enabled will be None|True|False.
            enabled = request.data.get(notification.notification.short)
            if enabled is not None:
                serializer = self.get_serializer(notification,
                                                 partial=True,
                                                 data={'enabled': enabled})
                serializer.is_valid(raise_exception=True)
                serializer.save()
        return Response(self.get_serializer(queryset, many=True).data)
예제 #17
0
class InternalAddonSearchView(AddonSearchView):
    # AddonSearchView disables auth classes so we need to add it back.
    authentication_classes = [WebTokenAuthentication]

    # Similar to AddonSearchView but without the ReviewedContentFilter (
    # allowing unlisted, deleted, unreviewed addons to show up) and with
    # InternalSearchParameterFilter instead of SearchParameterFilter (allowing
    # to search by status).
    filter_backends = [
        SearchQueryFilter, InternalSearchParameterFilter, SortingFilter
    ]

    # Restricted to specific permissions.
    permission_classes = [
        AnyOf(GroupPermission(amo.permissions.ADMIN_TOOLS_VIEW),
              GroupPermission(amo.permissions.REVIEWER_ADMIN_TOOLS_VIEW))
    ]
    # Can display unlisted data.
    serializer_class = ESAddonSerializerWithUnlistedData
예제 #18
0
class AccountViewSet(RetrieveModelMixin, UpdateModelMixin, GenericViewSet):
    permission_classes = [
        ByHttpMethod({
            'get':
            AllowAny,
            'head':
            AllowAny,
            'options':
            AllowAny,  # Needed for CORS.
            # To edit a profile it has to yours, or be an admin.
            'patch':
            AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT)),
        }),
    ]
    queryset = UserProfile.objects.all()

    def get_object(self):
        if hasattr(self, 'instance'):
            return self.instance
        identifier = self.kwargs.get('pk')
        self.lookup_field = self.get_lookup_field(identifier)
        self.kwargs[self.lookup_field] = identifier
        self.instance = super(AccountViewSet, self).get_object()
        return self.instance

    def get_lookup_field(self, identifier):
        lookup_field = 'pk'
        if identifier and not identifier.isdigit():
            # If the identifier contains anything other than a digit, it's
            # the username.
            lookup_field = 'username'
        return lookup_field

    @property
    def self_view(self):
        return (self.request.user.is_authenticated()
                and self.get_object() == self.request.user)

    def get_serializer_class(self):
        if (self.self_view or acl.action_allowed_user(
                self.request.user, amo.permissions.USERS_EDIT)):
            return UserProfileSerializer
        else:
            return PublicUserProfileSerializer

    @list_route(permission_classes=[IsAuthenticated])
    def profile(self, request, *args, **kwargs):
        self.kwargs['pk'] = unicode(self.request.user.pk)
        return self.retrieve(request, *args, **kwargs)
예제 #19
0
class AccountNotificationViewSet(
    AccountNotificationMixin, ListModelMixin, GenericViewSet
):
    """Returns account notifications.

    If not already set by the user, defaults will be returned.
    """

    permission_classes = [IsAuthenticated]
    # We're pushing the primary permission checking to AccountViewSet for ease.
    account_permission_classes = [
        AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT))
    ]
    serializer_class = UserNotificationSerializer
    paginator = None

    def get_user(self):
        return self.get_account_viewset().get_object()

    def get_account_viewset(self):
        if not hasattr(self, 'account_viewset'):
            self.account_viewset = AccountViewSet(
                request=self.request,
                permission_classes=self.account_permission_classes,
                kwargs={'pk': self.kwargs['user_pk']},
            )
        return self.account_viewset

    def create(self, request, *args, **kwargs):
        # Loop through possible notifications.
        queryset = self.get_queryset()
        for notification in queryset:
            # Careful with ifs.  `enabled` will be None|True|False.
            enabled = request.data.get(notification.notification.short)
            if enabled is not None:
                serializer = self.get_serializer(
                    notification, partial=True, data={'enabled': enabled}
                )
                serializer.is_valid(raise_exception=True)
                serializer.save()
        return Response(self.get_serializer(queryset, many=True).data)
예제 #20
0
class ScannerResultViewSet(ListAPIView):
    permission_classes = [
        GroupPermission(amo.permissions.ADMIN_SCANNERS_RESULTS_VIEW)
    ]

    serializer_class = ScannerResultSerializer

    def get_queryset(self):
        good_results = (
            ScannerResult.objects.exclude(version=None)
            .exclude(
                version__versionlog__activity_log__user_id=settings.TASK_USER_ID  # noqa
            )
            .filter(
                version__versionlog__activity_log__action__in=(
                    amo.LOG.CONFIRM_AUTO_APPROVED.id,
                    amo.LOG.APPROVE_VERSION.id,
                )
            )
            .annotate(label=Value(LABEL_GOOD, output_field=CharField()))
            .all()
        )
        bad_results = (
            ScannerResult.objects.exclude(version=None)
            .filter(state=TRUE_POSITIVE)
            .annotate(label=Value(LABEL_BAD, output_field=CharField()))
            .all()
        )
        return good_results.union(bad_results).order_by('-created')

    def get(self, request, format=None):
        if not waffle.switch_is_active('enable-scanner-results-api'):
            raise Http404
        return super().get(request, format)

    @classmethod
    def as_view(cls, **initkwargs):
        """The API is read-only so we can turn off atomic requests."""
        return non_atomic_requests(
            super(ScannerResultViewSet, cls).as_view(**initkwargs)
        )
예제 #21
0
class AppVersionView(APIView):
    authentication_classes = [JWTKeyAuthentication]
    permission_classes = [GroupPermission(amo.permissions.APPVERSIONS_CREATE)]

    def put(self, request, *args, **kwargs):
        # For each request, we'll try to create up to 3 versions, one for the
        # parameter in the URL, one for the corresponding "release" version if
        # it's different (if 79.0a1 is passed, the base would be 79.0. If 79.0
        # is passed, then we'd skip that one as they are the same) and a last
        # one for the corresponding max version with a star (if 79.0 or 79.0a1
        # is passed, then this would be 79.*)
        application = amo.APPS.get(kwargs.get('application'))
        if not application:
            raise ParseError('Invalid application parameter')
        requested_version = kwargs.get('version')
        if not requested_version or not version_re.match(requested_version):
            raise ParseError('Invalid version parameter')
        version_data = version_dict(requested_version)
        release_version = '%d.%d' % (version_data['major'],
                                     version_data['minor1'] or 0)
        star_version = '%d.*' % version_data['major']
        _, created_requested = AppVersion.objects.get_or_create(
            application=application.id, version=requested_version)
        if requested_version != release_version:
            _, created_release = AppVersion.objects.get_or_create(
                application=application.id, version=release_version)
        else:
            created_release = False
        if requested_version != star_version:
            _, created_star = AppVersion.objects.get_or_create(
                application=application.id, version=star_version)
        else:
            created_star = False
        created = created_requested or created_release or created_star
        status_code = HTTP_201_CREATED if created else HTTP_202_ACCEPTED
        return Response(status=status_code)
예제 #22
0
 def test_user_cannot_be_anonymous(self):
     request = RequestFactory().get('/')
     request.user = AnonymousUser()
     view = Mock()
     perm = GroupPermission('SomeRealm', 'SomePermission')
     assert perm.has_permission(request, view) is False
예제 #23
0
class AddonViewSet(RetrieveModelMixin, GenericViewSet):
    permission_classes = [
        AnyOf(
            AllowReadOnlyIfPublic,
            AllowAddonAuthor,
            AllowReviewer,
            AllowReviewerUnlisted,
        ),
    ]
    georestriction_classes = [
        RegionalRestriction | GroupPermission(amo.permissions.ADDONS_EDIT)
    ]
    serializer_class = AddonSerializer
    serializer_class_with_unlisted_data = AddonSerializerWithUnlistedData
    lookup_value_regex = '[^/]+'  # Allow '.' for email-like guids.

    def get_queryset(self):
        """Return queryset to be used for the view."""
        # Special case: admins - and only admins - can see deleted add-ons.
        # This is handled outside a permission class because that condition
        # would pollute all other classes otherwise.
        if self.request.user.is_authenticated and acl.action_allowed(
                self.request, amo.permissions.ADDONS_VIEW_DELETED):
            qs = Addon.unfiltered.all()
        else:
            # Permission classes disallow access to non-public/unlisted add-ons
            # unless logged in as a reviewer/addon owner/admin, so we don't
            # have to filter the base queryset here.
            qs = Addon.objects.all()
        if self.action == 'retrieve_from_related':
            # Avoid default transformers if we're fetching a single instance
            # from a related view: We're unlikely to need the preloading they
            # bring, this would only cause extra useless queries. Still include
            # translations because at least the addon name is likely to be
            # needed in most cases.
            qs = qs.only_translations()
        return qs

    def get_serializer_class(self):
        # Override serializer to use serializer_class_with_unlisted_data if
        # we are allowed to access unlisted data.
        obj = getattr(self, 'instance')
        request = self.request
        if acl.check_unlisted_addons_reviewer(request) or (
                obj and request.user.is_authenticated
                and obj.authors.filter(pk=request.user.pk).exists()):
            return self.serializer_class_with_unlisted_data
        return self.serializer_class

    def get_lookup_field(self, identifier):
        return Addon.get_lookup_field(identifier)

    def get_object(self):
        identifier = self.kwargs.get('pk')
        self.lookup_field = self.get_lookup_field(identifier)
        self.kwargs[self.lookup_field] = identifier
        self.instance = super(AddonViewSet, self).get_object()
        return self.instance

    def check_permissions(self, request):
        for restriction in self.get_georestrictions():
            if not restriction.has_permission(request, self):
                raise UnavailableForLegalReasons()

        super().check_permissions(request)

    def check_object_permissions(self, request, obj):
        """
        Check if the request should be permitted for a given object.
        Raises an appropriate exception if the request is not permitted.

        Calls DRF implementation, but adds `is_disabled_by_developer` and
        `is_disabled_by_mozilla` to the exception being thrown so that clients
        can tell the difference between a 401/403 returned because an add-on
        has been disabled by their developer or something else.
        """
        for restriction in self.get_georestrictions():
            if not restriction.has_object_permission(request, self, obj):
                raise UnavailableForLegalReasons()

        try:
            super(AddonViewSet, self).check_object_permissions(request, obj)
        except exceptions.APIException as exc:
            # Override exc.detail with a dict so that it's returned as-is in
            # the response. The base implementation for exc.get_codes() does
            # not expect dicts in that format, so override it as well with a
            # lambda that returns what would have been returned before our
            # changes.
            codes = exc.get_codes()
            exc.get_codes = lambda: codes
            exc.detail = {
                'detail': exc.detail,
                'is_disabled_by_developer': obj.disabled_by_user,
                'is_disabled_by_mozilla': obj.status == amo.STATUS_DISABLED,
            }
            raise exc

    def get_georestrictions(self):
        return [perm() for perm in self.georestriction_classes]

    @action(detail=True)
    def eula_policy(self, request, pk=None):
        obj = self.get_object()
        serializer = AddonEulaPolicySerializer(
            obj, context=self.get_serializer_context())
        return Response(serializer.data)
예제 #24
0
 def test_user_cannot_be_anonymous(self):
     request = RequestFactory().get('/')
     request.user = Mock(is_authenticated=Mock(return_value=False))
     view = Mock()
     perm = GroupPermission('SomeRealm', 'SomePermission')
     assert perm.has_permission(request, view) == False
예제 #25
0
class AccountNotificationViewSet(ListModelMixin, GenericViewSet):
    """Returns account notifications.

    If not already set by the user, defaults will be returned.
    """

    permission_classes = [IsAuthenticated]
    # We're pushing the primary permission checking to AccountViewSet for ease.
    account_permission_classes = [
        AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT))
    ]
    serializer_class = UserNotificationSerializer
    paginator = None

    def get_account_viewset(self):
        if not hasattr(self, 'account_viewset'):
            self.account_viewset = AccountViewSet(
                request=self.request,
                permission_classes=self.account_permission_classes,
                kwargs={'pk': self.kwargs['user_pk']})
        return self.account_viewset

    def _get_default_object(self, notification):
        return UserNotification(user=self.get_account_viewset().get_object(),
                                notification_id=notification.id,
                                enabled=notification.default_checked)

    def get_queryset(self):
        user = self.get_account_viewset().get_object()
        queryset = UserNotification.objects.filter(user=user)

        # Fetch all `UserNotification` instances and then, if the
        # waffle-switch is active overwrite their value with the
        # data from basket. Once we switched the integration "on" on prod
        # all `UserNotification` instances that are now handled by basket
        # can be deleted.

        # Put it into a dict so we can easily check for existence.
        set_notifications = {
            user_nfn.notification.short: user_nfn
            for user_nfn in queryset
        }
        out = []

        if waffle.switch_is_active('activate-basket-sync'):
            newsletters = None  # Lazy - fetch the first time needed.
            by_basket_id = REMOTE_NOTIFICATIONS_BY_BASKET_ID
            for basket_id, notification in by_basket_id.items():
                if notification.group == 'dev' and not user.is_developer:
                    # We only return dev notifications for developers.
                    continue
                if newsletters is None:
                    newsletters = fetch_subscribed_newsletters(user)
                user_notification = self._get_default_object(notification)
                user_notification.enabled = basket_id in newsletters
                set_notifications[notification.short] = user_notification

        for notification in NOTIFICATIONS_COMBINED:
            if notification.group == 'dev' and not user.is_developer:
                # We only return dev notifications for developers.
                continue
            out.append(
                set_notifications.get(
                    notification.short,  # It's been set by the user.
                    self._get_default_object(notification)))  # Or, default.
        return out

    def create(self, request, *args, **kwargs):
        # Loop through possible notifications.
        queryset = self.get_queryset()
        for notification in queryset:
            # Careful with ifs.  Enabled will be None|True|False.
            enabled = request.data.get(notification.notification.short)
            if enabled is not None:
                serializer = self.get_serializer(notification,
                                                 partial=True,
                                                 data={'enabled': enabled})
                serializer.is_valid(raise_exception=True)
                serializer.save()
        return Response(self.get_serializer(queryset, many=True).data)
예제 #26
0
class AccountViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin,
                     GenericViewSet):
    permission_classes = [
        ByHttpMethod({
            'get':
            AllowAny,
            'head':
            AllowAny,
            'options':
            AllowAny,  # Needed for CORS.
            # To edit a profile it has to yours, or be an admin.
            'patch':
            AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT)),
            'delete':
            AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT)),
        }),
    ]
    # Periods are not allowed in username, but we still have some in the
    # database so relax the lookup regexp to allow them to load their profile.
    lookup_value_regex = '[^/]+'

    def get_queryset(self):
        return UserProfile.objects.exclude(deleted=True).all()

    def get_object(self):
        if hasattr(self, 'instance'):
            return self.instance
        identifier = self.kwargs.get('pk')
        self.lookup_field = self.get_lookup_field(identifier)
        self.kwargs[self.lookup_field] = identifier
        self.instance = super(AccountViewSet, self).get_object()
        # action won't exist for other classes that are using this ViewSet.
        can_view_instance = (not getattr(self, 'action', None)
                             or self.self_view or self.admin_viewing
                             or self.instance.is_public)
        if can_view_instance:
            return self.instance
        else:
            raise Http404

    def get_lookup_field(self, identifier):
        lookup_field = 'pk'
        if identifier and not identifier.isdigit():
            # If the identifier contains anything other than a digit, it's
            # the username.
            lookup_field = 'username'
        return lookup_field

    @property
    def self_view(self):
        return (self.request.user.is_authenticated
                and self.get_object() == self.request.user)

    @property
    def admin_viewing(self):
        return acl.action_allowed_user(self.request.user,
                                       amo.permissions.USERS_EDIT)

    def get_serializer_class(self):
        if self.self_view or self.admin_viewing:
            return UserProfileSerializer
        else:
            return PublicUserProfileSerializer

    def destroy(self, request, *args, **kwargs):
        instance = self.get_object()
        self.perform_destroy(instance)
        response = Response(status=HTTP_204_NO_CONTENT)
        if instance == request.user:
            logout_user(request, response)
        return response

    @action(
        detail=True,
        methods=['delete'],
        permission_classes=[
            AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT))
        ],
    )
    def picture(self, request, pk=None):
        user = self.get_object()
        user.delete_picture()
        log.info('User (%s) deleted photo' % user)
        return self.retrieve(request)
예제 #27
0
 def test_user_cannot_be_anonymous(self):
     request = RequestFactory().get('/')
     request.user = AnonymousUser()
     view = Mock(spec=[])
     perm = GroupPermission(amo.permissions.NONE)
     assert not perm.has_permission(request, view)
예제 #28
0
class RatingViewSet(AddonChildMixin, ModelViewSet):
    serializer_class = RatingSerializer
    permission_classes = [
        ByHttpMethod({
            'get': AllowAny,
            'head': AllowAny,
            'options': AllowAny,  # Needed for CORS.

            # Deletion requires a specific permission check.
            'delete': CanDeleteRatingPermission,

            # To post a rating you just need to be authenticated.
            'post': IsAuthenticated,

            # To edit a rating you need to be the author or be an admin.
            'patch': AnyOf(AllowOwner, GroupPermission(
                amo.permissions.ADDONS_EDIT)),

            # Implementing PUT would be a little incoherent as we don't want to
            # allow users to change `version` but require it at creation time.
            # So only PATCH is allowed for editing.
        }),
    ]
    reply_permission_classes = [AnyOf(
        GroupPermission(amo.permissions.ADDONS_EDIT),
        AllowRelatedObjectPermissions('addon', [AllowAddonAuthor]),
    )]
    reply_serializer_class = RatingSerializerReply
    throttle_classes = (RatingThrottle,)

    def set_addon_object_from_rating(self, rating):
        """Set addon object on the instance from a rating object."""
        # At this point it's likely we didn't have an addon in the request, so
        # if we went through get_addon_object() before it's going to be set
        # to None already. We delete the addon_object property cache and set
        # addon_pk in kwargs to force get_addon_object() to reset
        # self.addon_object.
        del self.addon_object
        self.kwargs['addon_pk'] = str(rating.addon.pk)
        return self.get_addon_object()

    def get_addon_object(self):
        """Return addon object associated with the request, or None if not
        relevant.

        Will also fire permission checks on the addon object when it's loaded.
        """
        if hasattr(self, 'addon_object'):
            return self.addon_object

        if 'addon_pk' not in self.kwargs:
            self.kwargs['addon_pk'] = (
                self.request.data.get('addon') or
                self.request.GET.get('addon'))
        if not self.kwargs['addon_pk']:
            # If we don't have an addon object, set it as None on the instance
            # and return immediately, that's fine.
            self.addon_object = None
            return
        else:
            # AddonViewSet.get_lookup_field() expects a string.
            self.kwargs['addon_pk'] = force_text(self.kwargs['addon_pk'])
        # When loading the add-on, pass a specific permission class - the
        # default from AddonViewSet is too restrictive, we are not modifying
        # the add-on itself so we don't need all the permission checks it does.
        return super(RatingViewSet, self).get_addon_object(
            permission_classes=[AllowIfPublic])

    def check_permissions(self, request):
        """Perform permission checks.

        The regular DRF permissions checks are made, but also, before that, if
        an addon was requested, verify that it exists, is public and listed,
        through AllowIfPublic permission, that get_addon_object() uses."""
        self.get_addon_object()

        # Proceed with the regular permission checks.
        return super(RatingViewSet, self).check_permissions(request)

    def get_serializer(self, *args, **kwargs):
        if self.action in ('partial_update', 'update'):
            instance = args[0]
            if instance.reply_to is not None:
                self.rating_object = instance.reply_to
                self.serializer_class = self.reply_serializer_class
        return super(RatingViewSet, self).get_serializer(*args, **kwargs)

    def filter_queryset(self, qs):
        if self.action == 'list':
            addon_identifier = self.request.GET.get('addon')
            user_identifier = self.request.GET.get('user')
            version_identifier = self.request.GET.get('version')
            if addon_identifier:
                qs = qs.filter(addon=self.get_addon_object())
            if user_identifier:
                try:
                    user_identifier = int(user_identifier)
                except ValueError:
                    raise ParseError('user parameter should be an integer.')
                qs = qs.filter(user=user_identifier)
            if version_identifier:
                try:
                    version_identifier = int(version_identifier)
                except ValueError:
                    raise ParseError('version parameter should be an integer.')
                qs = qs.filter(version=version_identifier)
            elif addon_identifier:
                # When filtering on addon but not on version, only return the
                # latest rating posted by each user.
                qs = qs.filter(is_latest=True)
            if not addon_identifier and not user_identifier:
                # Don't allow listing ratings without filtering by add-on or
                # user.
                raise ParseError('Need an addon or user parameter')
            if user_identifier and addon_identifier and version_identifier:
                # When user, addon and version identifiers are set, we are
                # effectively only looking for one or zero objects. Fake
                # pagination in that case, avoiding all count() calls and
                # therefore related cache-machine invalidation issues. Needed
                # because the frontend wants to call this before and after
                # having posted a new rating, and needs accurate results.
                self.pagination_class = OneOrZeroPageNumberPagination
        return super(RatingViewSet, self).filter_queryset(qs)

    def get_paginated_response(self, data):
        response = super(RatingViewSet, self).get_paginated_response(data)
        if 'show_grouped_ratings' in self.request.GET:
            try:
                show_grouped_ratings = (
                    serializers.BooleanField().to_internal_value(
                        self.request.GET['show_grouped_ratings']))
            except serializers.ValidationError:
                raise ParseError(
                    'show_grouped_ratings parameter should be a boolean')
            if show_grouped_ratings and self.get_addon_object():
                response.data['grouped_ratings'] = dict(GroupedRating.get(
                    self.addon_object.id))
        return response

    def get_queryset(self):
        requested = self.request.GET.get('filter', '').split(',')
        has_addons_edit = acl.action_allowed(self.request,
                                             amo.permissions.ADDONS_EDIT)

        # Add this as a property of the view, because we need to pass down the
        # information to the serializer to show/hide delete replies.
        if not hasattr(self, 'should_access_deleted_ratings'):
            self.should_access_deleted_ratings = (
                ('with_deleted' in requested or self.action != 'list') and
                self.request.user.is_authenticated() and
                has_addons_edit)

        should_access_only_top_level_ratings = (
            self.action == 'list' and self.get_addon_object())

        if self.should_access_deleted_ratings:
            # For admins or add-on authors replying. When listing, we include
            # deleted ratings but still filter out out replies, because they'll
            # be in the serializer anyway. For other actions, we simply remove
            # any filtering, allowing them to access any rating out of the box
            # with no extra parameter needed.
            if self.action == 'list':
                queryset = Rating.unfiltered.filter(reply_to__isnull=True)
            else:
                queryset = Rating.unfiltered.all()
        elif should_access_only_top_level_ratings:
            # When listing add-on ratings, exclude replies, they'll be
            # included during serialization as children of the relevant
            # ratings instead.
            queryset = Rating.without_replies.all()
        else:
            queryset = Rating.objects.all()

        # Filter out empty ratings if specified.
        # Should the users own empty ratings be filtered back in?
        if 'with_yours' in requested and self.request.user.is_authenticated():
            user_filter = Q(user=self.request.user.pk)
        else:
            user_filter = Q()
        # Apply the filter(s)
        if 'without_empty_body' in requested:
            queryset = queryset.filter(~Q(body=None) | user_filter)

        # The serializer needs reply, version and user. We don't need much
        # for version and user, so we can make joins with select_related(),
        # but for replies additional queries will be made for translations
        # anyway so we're better off using prefetch_related() to make a
        # separate query to fetch them all.
        queryset = queryset.select_related('version', 'user')
        replies_qs = Rating.unfiltered.select_related('user')
        return queryset.prefetch_related(
            Prefetch('reply', queryset=replies_qs))

    @detail_route(
        methods=['post'], permission_classes=reply_permission_classes,
        serializer_class=reply_serializer_class,
        throttle_classes=[RatingReplyThrottle])
    def reply(self, *args, **kwargs):
        # A reply is just like a regular post, except that we set the reply
        # FK to the current rating object and only allow add-on authors/admins.
        # Call get_object() to trigger 404 if it does not exist.
        self.rating_object = self.get_object()
        self.set_addon_object_from_rating(self.rating_object)
        if Rating.unfiltered.filter(reply_to=self.rating_object).exists():
            # A reply already exists, just edit it.
            # We set should_access_deleted_ratings so that it works even if
            # the reply has been deleted.
            self.kwargs['pk'] = kwargs['pk'] = self.rating_object.reply.pk
            self.should_access_deleted_ratings = True
            return self.partial_update(*args, **kwargs)
        return self.create(*args, **kwargs)

    @detail_route(methods=['post'], throttle_classes=[])
    def flag(self, request, *args, **kwargs):
        # We load the add-on object from the rating to trigger permission
        # checks.
        self.rating_object = self.get_object()
        self.set_addon_object_from_rating(self.rating_object)

        # Re-use flag view since it's already returning json. We just need to
        # pass it the addon slug (passing it the PK would result in a redirect)
        # and make sure request.POST is set with whatever data was sent to the
        # DRF view.
        request._request.POST = request.data
        request = request._request
        response = flag(request, self.addon_object.slug, kwargs.get('pk'))
        if response.status_code == 200:
            # 202 is a little better than 200: we're accepting the request, but
            # make no promises to act on it :)
            response.status_code = 202
        return response

    def perform_destroy(self, instance):
        instance.delete(user_responsible=self.request.user)
예제 #29
0
 def test_user_cannot_be_anonymous(self):
     request = RequestFactory().get('/')
     request.user = AnonymousUser()
     view = Mock(spec=[])
     perm = GroupPermission(amo.permissions.NONE)
     assert not perm.has_permission(request, view)
예제 #30
0
class ProtectedView(APIView):
    permission_classes = [GroupPermission(amo.permissions.NONE)]

    def get(self, request):
        return Response('ok')
예제 #31
0
 def test_user_cannot_be_anonymous(self):
     request = RequestFactory().get('/')
     request.user = AnonymousUser()
     view = Mock()
     perm = GroupPermission('SomeRealm', 'SomePermission')
     assert not perm.has_permission(request, view)
예제 #32
0
class AccountViewSet(RetrieveModelMixin, UpdateModelMixin, DestroyModelMixin,
                     GenericViewSet):
    permission_classes = [
        ByHttpMethod({
            'get':
            AllowAny,
            'head':
            AllowAny,
            'options':
            AllowAny,  # Needed for CORS.
            # To edit a profile it has to yours, or be an admin.
            'patch':
            AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT)),
            'delete':
            AnyOf(AllowSelf, GroupPermission(amo.permissions.USERS_EDIT)),
        }),
    ]

    def get_queryset(self):
        return UserProfile.objects.all()

    def get_object(self):
        if hasattr(self, 'instance'):
            return self.instance
        identifier = self.kwargs.get('pk')
        self.lookup_field = self.get_lookup_field(identifier)
        self.kwargs[self.lookup_field] = identifier
        self.instance = super(AccountViewSet, self).get_object()
        return self.instance

    def get_lookup_field(self, identifier):
        lookup_field = 'pk'
        if identifier and not identifier.isdigit():
            # If the identifier contains anything other than a digit, it's
            # the username.
            lookup_field = 'username'
        return lookup_field

    @property
    def self_view(self):
        return (self.request.user.is_authenticated()
                and self.get_object() == self.request.user)

    def get_serializer_class(self):
        if (self.self_view or acl.action_allowed_user(
                self.request.user, amo.permissions.USERS_EDIT)):
            return UserProfileSerializer
        else:
            return PublicUserProfileSerializer

    def perform_destroy(self, instance):
        if instance.is_developer:
            raise serializers.ValidationError(
                ugettext(
                    u'Developers of add-ons or themes cannot delete their '
                    u'account. You must delete all add-ons and themes linked to '
                    u'this account, or transfer them to other users.'))
        return super(AccountViewSet, self).perform_destroy(instance)

    @detail_route(methods=['delete'],
                  permission_classes=[
                      AnyOf(AllowSelf,
                            GroupPermission(amo.permissions.USERS_EDIT))
                  ])
    def picture(self, request, pk=None):
        user = self.get_object()
        user.update(picture_type='')
        log.debug(u'User (%s) deleted photo' % user)
        tasks.delete_photo.delay(user.picture_path)
        return self.retrieve(request)
예제 #33
0
class ScannerResultView(ListAPIView):
    permission_classes = [
        GroupPermission(amo.permissions.ADMIN_SCANNERS_RESULTS_VIEW)
    ]

    serializer_class = ScannerResultSerializer

    def get_queryset(self):
        label = self.request.query_params.get('label', None)
        scanner = next(
            (key for key in SCANNERS
             if SCANNERS.get(key) == self.request.query_params.get('scanner')),
            None,
        )

        bad_results = ScannerResult.objects.exclude(version=None)
        good_results = ScannerResult.objects.exclude(version=None)

        if scanner:
            bad_results = bad_results.filter(scanner=scanner)
            good_results = good_results.filter(scanner=scanner)

        good_results = (
            good_results.exclude(
                version__versionlog__activity_log__user_id=settings.
                TASK_USER_ID  # noqa
            ).filter(version__versionlog__activity_log__action__in=(
                amo.LOG.CONFIRM_AUTO_APPROVED.id,
                amo.LOG.APPROVE_VERSION.id,
            )).annotate(
                label=Value(LABEL_GOOD, output_field=CharField())).all())
        bad_results = (bad_results.filter(state=TRUE_POSITIVE).annotate(
            label=Value(LABEL_BAD, output_field=CharField())).all())

        queryset = ScannerResult.objects.none()

        if not label:
            queryset = good_results.union(bad_results)
        elif label == LABEL_GOOD:
            queryset = good_results
        elif label == LABEL_BAD:
            queryset = bad_results

        return queryset.order_by('-created')

    def get(self, request, format=None):
        if not waffle.switch_is_active('enable-scanner-results-api'):
            raise Http404

        label = self.request.query_params.get('label', None)
        if label is not None and label not in [LABEL_BAD, LABEL_GOOD]:
            raise ParseError("invalid value for label")

        scanner = self.request.query_params.get('scanner', None)
        if scanner is not None and scanner not in list(SCANNERS.values()):
            raise ParseError("invalid value for scanner")

        return super().get(request, format)

    @classmethod
    def as_view(cls, **initkwargs):
        """The API is read-only so we can turn off atomic requests."""
        return non_atomic_requests(
            super(ScannerResultView, cls).as_view(**initkwargs))
예제 #34
0
 def test_user_cannot_be_anonymous(self):
     request = RequestFactory().get("/")
     request.user = AnonymousUser()
     view = Mock()
     perm = GroupPermission("SomeRealm", "SomePermission")
     assert not perm.has_permission(request, view)