def check_stats_permission(request, addon, for_contributions=False): """ Check if user is allowed to view stats for ``addon``. Raises PermissionDenied if user is not allowed. """ # If public, non-contributions: everybody can view. if addon.public_stats and not for_contributions: return # Everything else requires an authenticated user. if not request.user.is_authenticated(): raise PermissionDenied if not for_contributions: # Only authors and Stats Viewers allowed. if (addon.has_author(request.user) or acl.action_allowed(request, 'Stats', 'View')): return else: # For contribution stats. # Only authors and Contribution Stats Viewers. if (addon.has_author(request.user) or acl.action_allowed(request, 'RevenueStats', 'View')): return raise PermissionDenied
def _queue(request, TableObj, tab, qs=None, unlisted=False, SearchForm=QueueSearchForm): if qs is None: qs = TableObj.Meta.model.objects.all() if SearchForm: if request.GET: search_form = SearchForm(request.GET) if search_form.is_valid(): qs = search_form.filter_qs(qs) else: search_form = SearchForm() is_searching = search_form.data.get('searching') else: search_form = None is_searching = False admin_reviewer = is_admin_reviewer(request) # Those restrictions will only work with our RawSQLModel, so we need to # make sure we're not dealing with a regular Django ORM queryset first. if hasattr(qs, 'sql_model'): if not is_searching and not admin_reviewer: qs = filter_admin_review_for_legacy_queue(qs) if not unlisted: if is_limited_reviewer(request): qs = qs.having( 'waiting_time_hours >=', amo.REVIEW_LIMITED_DELAY_HOURS) qs = filter_static_themes( qs, acl.action_allowed(request, amo.permissions.ADDONS_REVIEW), acl.action_allowed( request, amo.permissions.STATIC_THEMES_REVIEW)) # Most WebExtensions are picked up by auto_approve cronjob, they # don't need to appear in the queues, unless auto approvals have # been disabled for them. Webextension static themes aren't auto # approved. qs = qs.filter( Q(addon_type_id=amo.ADDON_STATICTHEME) | Q(**{'files.is_webextension': False}) | Q(**{'addons_addonreviewerflags.auto_approval_disabled': True}) ) order_by = request.GET.get('sort', TableObj.default_order_by()) if hasattr(TableObj, 'translate_sort_cols'): order_by = TableObj.translate_sort_cols(order_by) table = TableObj(data=qs, order_by=order_by) per_page = request.GET.get('per_page', REVIEWS_PER_PAGE) try: per_page = int(per_page) except ValueError: per_page = REVIEWS_PER_PAGE if per_page <= 0 or per_page > REVIEWS_PER_PAGE_MAX: per_page = REVIEWS_PER_PAGE page = paginate(request, table.rows, per_page=per_page) table.set_page(page) return render(request, 'reviewers/queue.html', context(request, table=table, page=page, tab=tab, search_form=search_form, point_types=amo.REVIEWED_AMO, unlisted=unlisted))
def test_tools_developer_and_admin(self): # Make them a developer. user = self.login('admin', get=True) AddonUser.objects.create(user=user, addon=Addon.objects.all()[0]) response = self.client.get(self.url, follow=True) assert response.status_code == 200 request = response.context['request'] assert request.user.is_developer assert acl.action_allowed(request, amo.permissions.ADDONS_REVIEW) assert acl.action_allowed(request, amo.permissions.LOCALIZER) assert acl.action_allowed(request, amo.permissions.ANY_ADMIN) expected = [ ('Tools', '#'), ('Manage My Submissions', reverse('devhub.addons')), ('Submit a New Add-on', reverse('devhub.submit.agreement')), ('Submit a New Theme', reverse('devhub.themes.submit')), ('Developer Hub', reverse('devhub.index')), ('Manage API Keys', reverse('devhub.api_key')), ('Reviewer Tools', reverse('reviewers.dashboard')), ('Admin Tools', reverse('zadmin.index')), ] check_links( expected, pq(response.content)('#aux-nav .tools a'), verify=False)
def user_can_delete_review(request, review): """Return whether or not the request.user can delete reviews. People who can delete reviews: * The original review author. * Reviewers with Ratings:Moderate, if the review has been flagged and they are not an author of this add-on. * Users in a group with "Users:Edit" or "Addons:Edit" privileges and they are not an author of this add-on. """ is_rating_author = review.user_id == request.user.id is_addon_author = review.addon.has_author(request.user) is_moderator = ( acl.action_allowed(request, amo.permissions.RATINGS_MODERATE) and review.editorreview ) can_edit_users_or_addons = ( acl.action_allowed(request, amo.permissions.USERS_EDIT) or acl.action_allowed(request, amo.permissions.ADDONS_EDIT) ) return ( is_rating_author or (not is_addon_author and (is_moderator or can_edit_users_or_addons)) )
def wrapper(request, *args, **kw): from olympia.access import acl if (acl.action_allowed(request, '*', '*') or not acl.action_allowed(request, 'Restricted', 'UGC')): return f(request, *args, **kw) else: raise PermissionDenied
def motd(request): form = None if acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit'): form = forms.MOTDForm( initial={'motd': get_config('editors_review_motd')}) motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit') data = context(request, form=form, motd_editable=motd_editable) return render(request, 'editors/motd.html', data)
def home(request): if (not acl.action_allowed(request, amo.permissions.ADDONS_REVIEW) and acl.action_allowed(request, amo.permissions.THEMES_REVIEW)): return http.HttpResponseRedirect(reverse('editors.themes.home')) motd_editable = acl.action_allowed( request, amo.permissions.ADDON_REVIEWER_MOTD_EDIT) durations = (('new', ugettext('New Add-ons (Under 5 days)')), ('med', ugettext('Passable (5 to 10 days)')), ('old', ugettext('Overdue (Over 10 days)'))) limited_reviewer = is_limited_reviewer(request) progress, percentage = _editor_progress(limited_reviewer=limited_reviewer) reviews_max_display = getattr(settings, 'EDITOR_REVIEWS_MAX_DISPLAY', 5) reviews_total = ActivityLog.objects.total_reviews()[:reviews_max_display] reviews_monthly = ( ActivityLog.objects.monthly_reviews()[:reviews_max_display]) reviews_total_count = ActivityLog.objects.user_approve_reviews( request.user).count() reviews_monthly_count = ( ActivityLog.objects.current_month_user_approve_reviews( request.user).count()) # Try to read user position from retrieved reviews. # If not available, query for it. reviews_total_position = ( ActivityLog.objects.user_position(reviews_total, request.user) or ActivityLog.objects.total_reviews_user_position(request.user)) reviews_monthly_position = ( ActivityLog.objects.user_position(reviews_monthly, request.user) or ActivityLog.objects.monthly_reviews_user_position(request.user)) limited_reviewer = is_limited_reviewer(request) data = context( request, reviews_total=reviews_total, reviews_monthly=reviews_monthly, reviews_total_count=reviews_total_count, reviews_monthly_count=reviews_monthly_count, reviews_total_position=reviews_total_position, reviews_monthly_position=reviews_monthly_position, new_editors=EventLog.new_editors(), eventlog=ActivityLog.objects.editor_events()[:6], progress=progress, percentage=percentage, durations=durations, reviews_max_display=reviews_max_display, motd_editable=motd_editable, queue_counts_total=queue_counts(admin_reviewer=True, limited_reviewer=limited_reviewer), ) return render(request, 'editors/home.html', data)
def context(request, **kw): admin_reviewer = is_admin_reviewer(request) extension_reviews = acl.action_allowed( request, amo.permissions.ADDONS_REVIEW) theme_reviews = acl.action_allowed( request, amo.permissions.STATIC_THEMES_REVIEW) ctx = { 'queue_counts': queue_counts(admin_reviewer=admin_reviewer, extension_reviews=extension_reviews, theme_reviews=theme_reviews), } ctx.update(base_context(**kw)) return ctx
def queue_tabnav_themes_interactive(context): """Tabnav for the interactive shiny theme queues.""" tabs = [] if acl.action_allowed(context['request'], 'Personas', 'Review'): tabs.append(( 'editors.themes.queue_themes', 'pending', _('Pending'), )) if acl.action_allowed(context['request'], 'SeniorPersonasTools', 'View'): tabs.append(( 'editors.themes.queue_flagged', 'flagged', _('Flagged'), )) tabs.append(( 'editors.themes.queue_rereview', 'rereview', _('Updates'), )) return tabs
def queue_tabnav(context): """Returns tuple of tab navigation for the queue pages. Each tuple contains three elements: (tab_code, page_url, tab_text) """ counts = context['queue_counts'] request = context['request'] listed = not context.get('unlisted') if listed: tabnav = [('nominated', 'queue_nominated', (ungettext('New Add-on ({0})', 'New Add-ons ({0})', counts['nominated']) .format(counts['nominated']))), ('pending', 'queue_pending', (ungettext('Update ({0})', 'Updates ({0})', counts['pending']) .format(counts['pending']))), ('moderated', 'queue_moderated', (ungettext('Moderated Review ({0})', 'Moderated Reviews ({0})', counts['moderated']) .format(counts['moderated'])))] if acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW): tabnav.append( ('auto_approved', 'queue_auto_approved', (ungettext('Auto Approved Add-on ({0})', 'Auto Approved Add-ons ({0})', counts['auto_approved']) .format(counts['auto_approved']))), ) if acl.action_allowed(request, amo.permissions.ADDONS_CONTENT_REVIEW): tabnav.append( ('content_review', 'queue_content_review', (ungettext('Content Review ({0})', 'Content Review ({0})', counts['content_review']) .format(counts['content_review']))), ) else: tabnav = [ ('all', 'unlisted_queue_all', ugettext('All Unlisted Add-ons')) ] return tabnav
def eventlog_detail(request, id): log = get_object_or_404(ActivityLog.objects.editor_events(), pk=id) review = None # I really cannot express the depth of the insanity incarnate in # our logging code... if len(log.arguments) > 1 and isinstance(log.arguments[1], Review): review = log.arguments[1] is_admin = acl.action_allowed(request, 'ReviewerAdminTools', 'View') can_undelete = review and review.deleted and ( is_admin or request.user.pk == log.user.pk) if request.method == 'POST': # A Form seems overkill for this. if request.POST['action'] == 'undelete': if not can_undelete: raise PermissionDenied ReviewerScore.award_moderation_points( log.user, review.addon, review.id, undo=True) review.undelete() return redirect('editors.eventlog.detail', id) data = context(request, log=log, can_undelete=can_undelete) return render(request, 'editors/eventlog_detail.html', data)
def reply(request, addon, review_id): is_admin = acl.action_allowed(request, 'Addons', 'Edit') is_author = acl.check_addon_ownership(request, addon, dev=True) if not (is_admin or is_author): raise PermissionDenied review = get_object_or_404(Review.objects, pk=review_id, addon=addon) form = forms.ReviewReplyForm(request.POST or None) if request.method == 'POST' and form.is_valid(): d = dict(reply_to=review, addon=addon, defaults=dict(user=request.user)) reply, new = Review.objects.get_or_create(**d) for key, val in _review_details(request, addon, form).items(): setattr(reply, key, val) reply.save() action = 'New' if new else 'Edited' log.debug('%s reply to %s: %s' % (action, review_id, reply.id)) if new: reply_url = helpers.url('addons.reviews.detail', addon.slug, review.id, add_prefix=False) data = {'name': addon.name, 'reply_title': reply.title, 'reply': reply.body, 'reply_url': helpers.absolutify(reply_url)} emails = [review.user.email] sub = u'Mozilla Add-on Developer Reply: %s' % addon.name send_mail('reviews/emails/reply_review.ltxt', sub, emails, Context(data), 'reply') return redirect(helpers.url('addons.reviews.detail', addon.slug, review_id)) ctx = dict(review=review, form=form, addon=addon) return render(request, 'reviews/reply.html', ctx)
def wrapper(request, *args, **kw): allow_access = ( acl.is_user_any_kind_of_reviewer(request.user) or acl.action_allowed(request, permissions.RATINGS_MODERATE)) if allow_access: return f(request, *args, **kw) raise PermissionDenied
def queue_moderated(request): qs = Review.objects.all().to_moderate().order_by('reviewflag__created') page = paginate(request, qs, per_page=20) motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit') flags = dict(ReviewFlag.FLAGS) reviews_formset = ReviewFlagFormSet(request.POST or None, queryset=page.object_list, request=request) if request.method == 'POST': if reviews_formset.is_valid(): reviews_formset.save() else: amo.messages.error( request, ' '.join(e.as_text() or _('An unknown error occurred') for e in reviews_formset.errors)) return redirect(reverse('editors.queue_moderated')) return render(request, 'editors/queue.html', context(request, reviews_formset=reviews_formset, tab='moderated', page=page, flags=flags, search_form=None, point_types=amo.REVIEWED_AMO, motd_editable=motd_editable))
def __init__(self, *args, **kw): self.addon = kw.pop('addon') self.request = kw.pop('request', None) super(BaseCategoryFormSet, self).__init__(*args, **kw) self.initial = [] apps = sorted(self.addon.compatible_apps.keys(), key=lambda x: x.id) # Drop any apps that don't have appropriate categories. qs = Category.objects.filter(type=self.addon.type) app_cats = {k: list(v) for k, v in sorted_groupby(qs, 'application')} for app in list(apps): if app and not app_cats.get(app.id): apps.remove(app) if not app_cats: apps = [] for app in apps: cats = self.addon.app_categories.get(app, []) self.initial.append({'categories': [c.id for c in cats]}) for app, form in zip(apps, self.forms): key = app.id if app else None form.request = self.request form.initial['application'] = key form.app = app cats = sorted(app_cats[key], key=lambda x: x.name) form.fields['categories'].choices = [(c.id, c.name) for c in cats] # If this add-on is featured for this application, category # changes are forbidden. if not acl.action_allowed(self.request, amo.permissions.ADDONS_EDIT): form.disabled = (app and self.addon.is_featured(app))
def queue_tabnav_themes_interactive(context): """Tabnav for the interactive shiny theme queues.""" tabs = [] if acl.action_allowed(context['request'], amo.permissions.THEMES_REVIEW): tabs.append(( 'editors.themes.queue_themes', 'pending', ugettext('Pending'), )) if acl.action_allowed(context['request'], amo.permissions.THEME_ADMIN_TOOLS_VIEW): tabs.append(( 'editors.themes.queue_flagged', 'flagged', ugettext('Flagged'), )) tabs.append(( 'editors.themes.queue_rereview', 'rereview', ugettext('Updates'), )) return tabs
def queue_moderated(request): # In addition to other checks, this only show reviews for public and # listed add-ons. Unlisted add-ons typically won't have reviews anyway # but they might if their status ever gets changed. rf = (Review.objects.exclude(Q(addon__isnull=True) | Q(addon__is_listed=False) | Q(reviewflag__isnull=True)) .filter(editorreview=1, addon__status__in=amo.LISTED_STATUSES) .order_by('reviewflag__created')) page = paginate(request, rf, per_page=20) motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit') flags = dict(ReviewFlag.FLAGS) reviews_formset = ReviewFlagFormSet(request.POST or None, queryset=page.object_list, request=request) if request.method == 'POST': if reviews_formset.is_valid(): reviews_formset.save() else: amo.messages.error( request, ' '.join(e.as_text() or _('An unknown error occurred') for e in reviews_formset.errors)) return redirect(reverse('editors.queue_moderated')) return render(request, 'editors/queue.html', context(request, reviews_formset=reviews_formset, tab='moderated', page=page, flags=flags, search_form=None, point_types=amo.REVIEWED_AMO, motd_editable=motd_editable))
def get_actions(self, request): actions = super().get_actions(request) if not acl.action_allowed(request, amo.permissions.ABUSEREPORTS_EDIT): # You need AbuseReports:Edit for the extra actions. actions.pop('mark_as_valid') actions.pop('mark_as_suspicious') return actions
def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} extra_context['can_ban'] = acl.action_allowed( request, amo.permissions.USERS_EDIT) return super(UserAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context, )
def queue_tabnav_themes(context): """Similar to queue_tabnav, but for themes.""" tabs = [] if acl.action_allowed(context['request'], 'Personas', 'Review'): tabs.append(( 'editors.themes.list', 'pending_themes', _('Pending'), )) if acl.action_allowed(context['request'], 'SeniorPersonasTools', 'View'): tabs.append(( 'editors.themes.list_flagged', 'flagged_themes', _('Flagged'), )) tabs.append(( 'editors.themes.list_rereview', 'rereview_themes', _('Updates'), )) return tabs
def review_list(request, addon, review_id=None, user_id=None): qs = Rating.without_replies.all().filter( addon=addon).order_by('-created') ctx = {'addon': addon, 'grouped_ratings': GroupedRating.get(addon.id)} ctx['form'] = forms.RatingForm(None) is_admin = acl.action_allowed(request, amo.permissions.ADDONS_EDIT) if review_id is not None: ctx['page'] = 'detail' # If this is a dev reply, find the first msg for context. review = get_object_or_404(Rating.objects.all(), pk=review_id) if review.reply_to_id: review_id = review.reply_to_id ctx['reply'] = review qs = qs.filter(pk=review_id) elif user_id is not None: ctx['page'] = 'user' qs = qs.filter(user=user_id) if not qs: raise http.Http404() else: ctx['page'] = 'list' qs = qs.filter(is_latest=True) # Don't filter out empty reviews for admins. if not is_admin: # But otherwise, filter out everyone elses empty reviews. user_filter = (Q(user=request.user.pk) if request.user.is_authenticated() else Q()) qs = qs.filter(~Q(body=None) | user_filter) ctx['reviews'] = reviews = paginate(request, qs) ctx['replies'] = Rating.get_replies(reviews.object_list) if request.user.is_authenticated(): ctx['review_perms'] = { 'is_admin': is_admin, 'is_reviewer': acl.action_allowed( request, amo.permissions.RATINGS_MODERATE), 'is_author': acl.check_addon_ownership(request, addon, viewer=True, dev=True, support=True), } ctx['flags'] = get_flags(request, reviews.object_list) else: ctx['review_perms'] = {} return render(request, 'ratings/review_list.html', ctx)
def get_actions(self, request, addon): actions = SortedDict() if request is None: # If request is not set, it means we are just (ab)using the # ReviewHelper for its `handler` attribute and we don't care about # the actions. return actions labels, details = self._review_actions() reviewable_because_complete = addon.status not in ( amo.STATUS_NULL, amo.STATUS_DELETED) reviewable_because_admin = ( not addon.admin_review or acl.action_allowed(request, 'ReviewerAdminTools', 'View')) reviewable_because_submission_time = ( not is_limited_reviewer(request) or (addon.latest_version is not None and addon.latest_version.nomination is not None and (datetime.datetime.now() - addon.latest_version.nomination >= datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS)))) reviewable_because_pending = addon.latest_version is not None and ( len(addon.latest_version.is_unreviewed) > 0 or addon.status == amo.STATUS_LITE_AND_NOMINATED) if (reviewable_because_complete and reviewable_because_admin and reviewable_because_submission_time and reviewable_because_pending): if self.review_type != 'preliminary': if addon.is_listed: label = _lazy('Push to public') else: label = _lazy('Grant full review') actions['public'] = {'method': self.handler.process_public, 'minimal': False, 'label': label} # An unlisted sideload add-on, which requests a full review, cannot # be granted a preliminary review. prelim_allowed = not waffle.flag_is_active( request, 'no-prelim-review') and addon.is_listed if prelim_allowed or self.review_type == 'preliminary': actions['prelim'] = { 'method': self.handler.process_preliminary, 'label': labels['prelim'], 'minimal': False} actions['reject'] = {'method': self.handler.process_sandbox, 'label': _lazy('Reject'), 'minimal': False} actions['info'] = {'method': self.handler.request_information, 'label': _lazy('Request more information'), 'minimal': True} actions['super'] = {'method': self.handler.process_super_review, 'label': _lazy('Request super-review'), 'minimal': True} actions['comment'] = {'method': self.handler.process_comment, 'label': _lazy('Comment'), 'minimal': True} for k, v in actions.items(): v['details'] = details.get(k) return actions
def wrapper(request, addon_id=None, *args, **kw): # Admins can see stats for every add-on regardless of its status. if acl.action_allowed(request, permissions.STATS_VIEW): qs = Addon.objects.all else: qs = Addon.objects.valid return addon_view(f, qs)(request, addon_id=addon_id, *args, **kw)
def _view_on_get(request): """Return True if the user can access this page. If the user is in a group with rule 'ReviewerTools:View' and the request is a GET request, they are allowed to view. """ return (request.method == 'GET' and acl.action_allowed(request, 'ReviewerTools', 'View'))
def has_object_permission(self, request, view, obj): can_access_because_viewer = ( request.method in SAFE_METHODS and acl.action_allowed(request, permissions.REVIEWER_TOOLS_VIEW)) can_access_because_listed_reviewer = ( obj.has_listed_versions() and acl.is_reviewer(request, obj)) return can_access_because_viewer or can_access_because_listed_reviewer
def _clean_upload(self): if not (self.cleaned_data['upload'].valid or self.cleaned_data['upload'].validation_timeout or self.cleaned_data['admin_override_validation'] and acl.action_allowed(self.request, 'ReviewerAdminTools', 'View')): raise forms.ValidationError(_(u'There was an error with your ' u'upload. Please try again.'))
def _view_on_get(request): """Return True if the user can access this page. If the user is in a group with rule 'ReviewerTools:View' and the request is a GET request, they are allowed to view. """ return (request.method == 'GET' and acl.action_allowed(request, amo.permissions.REVIEWER_TOOLS_VIEW))
def save_motd(request): if not acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit'): raise PermissionDenied form = forms.MOTDForm(request.POST) if form.is_valid(): set_config('editors_review_motd', form.cleaned_data['motd']) return redirect(reverse('editors.motd')) data = context(request, form=form) return render(request, 'editors/motd.html', data)
def _clean_upload(self): if not (self.cleaned_data['upload'].valid or self.cleaned_data['upload'].validation_timeout or self.cleaned_data['admin_override_validation'] and acl.action_allowed(self.request, amo.permissions.REVIEWER_ADMIN_TOOLS_VIEW)): raise forms.ValidationError( ugettext(u'There was an error with your upload. ' u'Please try again.'))
def wrapper(request, *args, **kw): admin = (action_allowed(request, 'Admin', '%') or action_allowed(request, 'AdminTools', 'View')) # Yes, the "is True" is here on purpose... because this decorator # takes optional arguments, but doesn't do it properly (so if # you're not giving it arguments, it takes the decorated function # as the first argument, and then "reviewers" is truthy. if reviewers is True: admin = ( admin or action_allowed(request, 'ReviewerAdminTools', 'View')) if theme_reviewers is True: admin = ( admin or action_allowed(request, 'SeniorPersonasTools', 'View')) if admin: return f(request, *args, **kw) raise PermissionDenied
def _queue(request, TableObj, tab, qs=None, unlisted=False): if qs is None: qs = TableObj.Meta.model.objects.all() if is_limited_reviewer(request): qs = qs.having('waiting_time_hours >=', REVIEW_LIMITED_DELAY_HOURS) if request.GET: search_form = forms.QueueSearchForm(request.GET) if search_form.is_valid(): qs = search_form.filter_qs(qs) else: search_form = forms.QueueSearchForm() admin_reviewer = is_admin_reviewer(request) if not admin_reviewer and not search_form.data.get('searching'): qs = exclude_admin_only_addons(qs) motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit') order_by = request.GET.get('sort', TableObj.default_order_by()) if hasattr(TableObj, 'translate_sort_cols'): order_by = TableObj.translate_sort_cols(order_by) table = TableObj(data=qs, order_by=order_by) default = 100 per_page = request.GET.get('per_page', default) try: per_page = int(per_page) except ValueError: per_page = default if per_page <= 0 or per_page > 200: per_page = default page = paginate(request, table.rows, per_page=per_page) table.set_page(page) return render(request, 'editors/queue.html', context(request, table=table, page=page, tab=tab, search_form=search_form, point_types=amo.REVIEWED_AMO, unlisted=unlisted, motd_editable=motd_editable))
def queue_moderated(request): # In addition to other checks, this only show reviews for public and # listed add-ons. Unlisted add-ons typically won't have reviews anyway # but they might if their status ever gets changed. rf = (Review.objects.exclude( Q(addon__isnull=True) | Q(addon__is_listed=False) | Q(reviewflag__isnull=True)).filter( editorreview=1, addon__status__in=amo.LISTED_STATUSES).order_by( 'reviewflag__created')) page = paginate(request, rf, per_page=20) motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit') flags = dict(ReviewFlag.FLAGS) reviews_formset = ReviewFlagFormSet(request.POST or None, queryset=page.object_list, request=request) if request.method == 'POST': if reviews_formset.is_valid(): reviews_formset.save() else: amo.messages.error( request, ' '.join(e.as_text() or _('An unknown error occurred') for e in reviews_formset.errors)) return redirect(reverse('editors.queue_moderated')) return render( request, 'editors/queue.html', context(request, reviews_formset=reviews_formset, tab='moderated', page=page, flags=flags, search_form=None, point_types=amo.REVIEWED_AMO, motd_editable=motd_editable))
def reviewlog(request): data = request.GET.copy() motd_editable = acl.action_allowed( request, amo.permissions.ADDON_REVIEWER_MOTD_EDIT) if not data.get('start') and not data.get('end'): today = date.today() data['start'] = date(today.year, today.month, 1) form = forms.ReviewLogForm(data) approvals = ActivityLog.objects.review_log() if not acl.check_unlisted_addons_reviewer(request): # Display logs related to unlisted versions only to senior reviewers. list_channel = amo.RELEASE_CHANNEL_LISTED approvals = approvals.filter(versionlog__version__channel=list_channel) if form.is_valid(): data = form.cleaned_data if data['start']: approvals = approvals.filter(created__gte=data['start']) if data['end']: approvals = approvals.filter(created__lt=data['end']) if data['search']: term = data['search'] approvals = approvals.filter( Q(commentlog__comments__icontains=term) | Q(addonlog__addon__name__localized_string__icontains=term) | Q(user__display_name__icontains=term) | Q(user__username__icontains=term)).distinct() pager = amo.utils.paginate(request, approvals, 50) data = context(request, form=form, pager=pager, motd_editable=motd_editable) return render(request, 'reviewers/reviewlog.html', data)
def download_source(request, version_id): """ Download source code for a given version_id. Requires developer of the add-on or admin reviewer permission. If the version or add-on is deleted, developers can't access. If the version source code wasn't provided, but the user had the right permissions, a 404 is raised. """ # Include deleted versions in the queryset, we'll check for that below. version = get_object_or_404(Version.unfiltered, pk=version_id) addon = version.addon # Channel doesn't matter, source code is only available to admin reviewers # or developers of the add-on. If the add-on, version or file is deleted or # disabled, then only admins can access. has_permission = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN) if (addon.status != amo.STATUS_DISABLED and not version.files.filter(status=amo.STATUS_DISABLED).exists() and not version.deleted and not addon.is_deleted): # Don't rely on 'admin' parameter for check_addon_ownership(), it # doesn't check the permission we want to check. has_permission = has_permission or acl.check_addon_ownership( request, addon, admin=False, dev=True) if not has_permission: raise http.Http404() response = HttpResponseXSendFile(request, version.source.path) path = version.source.path if not isinstance(path, str): path = path.decode('utf8') name = os.path.basename(path.replace('"', '')) disposition = 'attachment; filename="{0}"'.format(name).encode('utf8') response['Content-Disposition'] = disposition response['Access-Control-Allow-Origin'] = '*' return response
def check_stats_permission(request, addon, beta): """ Check if user is allowed to view stats/beta stats for ``addon``. Raises PermissionDenied if user is not allowed. Raises Http404 if user cannot access the beta mode (only if enabled). """ user = request.user user_cannot_access_beta = not user.is_authenticated or ( user.is_authenticated and not (user.email.endswith('@mozilla.com') or waffle.flag_is_active(request, 'beta-stats'))) addon_has_no_beta = addon.type not in amo.ADDON_TYPES_WITH_STATS if beta and (user_cannot_access_beta or addon_has_no_beta): raise http.Http404 can_view = user.is_authenticated and ( addon.has_author(user) or acl.action_allowed(request, amo.permissions.STATS_VIEW)) if not can_view: raise PermissionDenied
def change_view(self, request, object_id, form_url='', extra_context=None): extra_context = extra_context or {} extra_context['has_users_edit_permission'] = acl.action_allowed( request, amo.permissions.USERS_EDIT) lookup_field = UserProfile.get_lookup_field(object_id) if lookup_field != 'pk': try: if lookup_field == 'email': user = UserProfile.objects.get(email=object_id) except UserProfile.DoesNotExist: raise http.Http404 url = request.path.replace(object_id, str(user.id), 1) if request.GET: url += '?' + request.GET.urlencode() return http.HttpResponsePermanentRedirect(url) return super(UserAdmin, self).change_view( request, object_id, form_url, extra_context=extra_context, )
def __init__(self, *args, **kw): self.addon = kw.pop('addon') self.request = kw.pop('request', None) super(SingleCategoryForm, self).__init__(*args, **kw) form = self.fields['category'] # Hack because we know this is only used for Static Themes that only # support Firefox. Hoping to unify per-app categories in the meantime. app = amo.FIREFOX sorted_cats = sorted(CATEGORIES[app.id][self.addon.type].items(), key=lambda (slug, cat): slug) choices = [ (c.id, c.name) for _, c in sorted_cats] form.choices = choices # sorted(choices, key=lambda x: x.name) initials = self.addon.app_categories.get(app.id, []) if len(initials) > 0: form.initial = initials[0] # If this add-on is featured for this application, category changes are # forbidden. if not acl.action_allowed(self.request, amo.permissions.ADDONS_EDIT): form.disabled = (app and self.addon.is_featured(app))
def reply(request, addon, review_id): is_admin = acl.action_allowed(request, amo.permissions.ADDONS_EDIT) is_author = acl.check_addon_ownership(request, addon, dev=True) if not (is_admin or is_author): raise PermissionDenied rating = get_object_or_404(Rating.objects, pk=review_id, addon=addon) form = forms.RatingReplyForm(request.POST or None) if request.method == 'POST' and form.is_valid(): kwargs = { 'reply_to': rating, 'addon': addon, 'defaults': _review_details(request, addon, form) } reply, created = Rating.unfiltered.update_or_create(**kwargs) return redirect(jinja_helpers.url( 'addons.ratings.detail', addon.slug, review_id)) ctx = { 'review': rating, 'form': form, 'addon': addon } return render(request, 'ratings/reply.html', ctx)
def policy_viewer(request, addon, eula_or_privacy, page_title, long_title): if not eula_or_privacy: raise http.Http404 channel_text = request.GET.get('channel') channel, content_review_only = determine_channel(channel_text) # It's a read-only view so we can bypass the specific permissions checks # if we have ReviewerTools:View. bypass_more_specific_permissions_because_read_only = (acl.action_allowed( request, amo.permissions.REVIEWER_TOOLS_VIEW)) if not bypass_more_specific_permissions_because_read_only: perform_review_permission_checks( request, addon, channel, content_review_only=content_review_only) review_url = reverse('reviewers.review', args=(channel_text or 'listed', addon.slug if addon.slug else addon.pk)) return render( request, 'reviewers/policy_view.html', { 'addon': addon, 'review_url': review_url, 'content': eula_or_privacy, 'page_title': page_title, 'long_title': long_title })
def handle_false_positive(self, request, pk, *args, **kwargs): is_admin = acl.action_allowed( request, amo.permissions.ADMIN_SCANNERS_RESULTS_EDIT) if not is_admin or request.method != "POST": raise Http404 result = self.get_object(request, pk) result.update(state=FALSE_POSITIVE) messages.add_message( request, messages.INFO, 'Scanner result {} has been marked as false positive.'.format(pk), ) title = 'False positive report for ScannerResult {}'.format(pk) body = render_to_string('admin/false_positive_report.md', { 'result': result, 'YARA': YARA }) labels = ','.join([ # Default label added to all issues 'false positive report' ] + [ 'rule: {}'.format(rule.name) for rule in result.matched_rules.all() ]) return redirect('https://github.com/{}/issues/new?{}'.format( result.get_git_repository(), urlencode({ 'title': title, 'body': body, 'labels': labels }), ))
def spam(request): if not acl.action_allowed(request, 'Spam', 'Flag'): raise PermissionDenied spam = Spam() if request.method == 'POST': review = Review.objects.get(pk=request.POST['review']) if 'del_review' in request.POST: log.info('SPAM: %s' % review.id) delete(request, request.POST['addon'], review.id) messages.success(request, 'Deleted that review.') elif 'del_user' in request.POST: user = review.user log.info('SPAMMER: %s deleted %s' % (request.user.username, user.username)) if not user.is_developer: Review.objects.filter(user=user).delete() user.anonymize() messages.success(request, 'Deleted that dirty spammer.') for reason in spam.reasons(): spam.redis.srem(reason, review.id) return http.HttpResponseRedirect(request.path) buckets = {} for reason in spam.reasons(): ids = spam.redis.smembers(reason) key = reason.split(':')[-1] buckets[key] = Review.objects.no_cache().filter(id__in=ids) reviews = dict((review.addon_id, review) for bucket in buckets.values() for review in bucket) for addon in Addon.objects.no_cache().filter(id__in=reviews): reviews[addon.id].addon = addon return render(request, 'reviews/spam.html', dict(buckets=buckets, review_perms=dict(is_admin=True)))
def review(request, addon): if not addon.is_listed and not acl.check_unlisted_addons_reviewer(request): raise http.Http404 version = addon.find_latest_version_including_rejected() if not settings.ALLOW_SELF_REVIEWS and addon.has_author(request.user): amo.messages.warning(request, _('Self-reviews are not allowed.')) return redirect(reverse('editors.queue')) form_helper = ReviewHelper(request=request, addon=addon, version=version) form = forms.ReviewForm(request.POST or None, helper=form_helper) if addon.is_listed: queue_type = form.helper.review_type redirect_url = reverse('editors.queue_%s' % queue_type) else: redirect_url = reverse('editors.unlisted_queue_all') is_admin = acl.action_allowed(request, 'Addons', 'Edit') if request.method == 'POST' and form.is_valid(): form.helper.process() if form.cleaned_data.get('notify'): EditorSubscription.objects.get_or_create(user=request.user, addon=addon) if form.cleaned_data.get('adminflag') and is_admin: addon.update(admin_review=False) amo.messages.success(request, _('Review successfully processed.')) clear_review_reviewing_cache(addon.id) return redirect(redirect_url) # Kick off validation tasks for any files in this version which don't have # cached validation, since editors will almost certainly need to access # them. But only if we're not running in eager mode, since that could mean # blocking page load for several minutes. if version and not getattr(settings, 'CELERY_ALWAYS_EAGER', False): for file_ in version.files.all(): if not file_.has_been_validated: devhub_tasks.validate(file_) canned = AddonCannedResponse.objects.all() actions = form.helper.actions.items() try: show_diff = version and (addon.versions.exclude(id=version.id).filter( files__isnull=False, created__lt=version.created, files__status=amo.STATUS_PUBLIC).latest()) except Version.DoesNotExist: show_diff = None # The actions we should show a minimal form from. actions_minimal = [k for (k, a) in actions if not a.get('minimal')] versions = (Version.unfiltered.filter(addon=addon).exclude( files__status=amo.STATUS_BETA).order_by('-created').transform( Version.transformer_activity).transform(Version.transformer)) class PseudoVersion(object): def __init__(self): self.all_activity = [] all_files = () approvalnotes = None compatible_apps_ordered = () releasenotes = None status = 'Deleted' deleted = True channel = amo.RELEASE_CHANNEL_LISTED @property def created(self): return self.all_activity[0].created @property def version(self): return (self.all_activity[0].activity_log.details.get( 'version', '[deleted]')) # Grab review history for deleted versions of this add-on # Version are soft-deleted now but we need this for older deletions. comments = (CommentLog.objects.filter( activity_log__action__in=amo.LOG_REVIEW_QUEUE, activity_log__versionlog=None, activity_log__addonlog__addon=addon).order_by( 'created').select_related('activity_log')) comment_versions = defaultdict(PseudoVersion) for c in comments: c.version = c.activity_log.details.get('version', c.created) comment_versions[c.version].all_activity.append(c) all_versions = comment_versions.values() all_versions.extend(versions) all_versions.sort(key=lambda v: v.created, reverse=True) pager = amo.utils.paginate(request, all_versions, 10) num_pages = pager.paginator.num_pages count = pager.paginator.count try: flags = ViewQueue.objects.get(id=addon.id).flags except ViewQueue.DoesNotExist: flags = [] user_changes_actions = [ amo.LOG.ADD_USER_WITH_ROLE.id, amo.LOG.CHANGE_USER_WITH_ROLE.id, amo.LOG.REMOVE_USER_WITH_ROLE.id ] user_changes_log = AddonLog.objects.filter( activity_log__action__in=user_changes_actions, addon=addon).order_by('id') ctx = context(request, version=version, addon=addon, pager=pager, num_pages=num_pages, count=count, flags=flags, form=form, canned=canned, is_admin=is_admin, show_diff=show_diff, actions=actions, actions_minimal=actions_minimal, whiteboard_form=forms.WhiteboardForm(instance=addon), user_changes=user_changes_log, unlisted=not addon.is_listed) return render(request, 'editors/review.html', ctx)
def get_tags(self, addon): if acl.action_allowed(self.request, amo.permissions.ADDONS_EDIT): return list(addon.tags.values_list('tag_text', flat=True)) else: return list(addon.tags.filter(restricted=False) .values_list('tag_text', flat=True))
def queue_tabnav(context): """Returns tuple of tab navigation for the queue pages. Each tuple contains three elements: (tab_code, page_url, tab_text) """ counts = context['queue_counts'] request = context['request'] listed = not context.get('unlisted') if listed: tabnav = [] if acl.action_allowed( request, amo.permissions.ADDONS_RECOMMENDED_REVIEW): tabnav.append( ('recommended', 'queue_recommended', ugettext('Recommended ({0})').format(counts['recommended'])), ) if acl.action_allowed(request, amo.permissions.ADDONS_REVIEW): new_text = ugettext('Other Pending Review ({0})') tabnav.extend(( ('extension', 'queue_extension', '🛠️ ' + new_text.format(counts['extension'])), )) tabnav.append(( 'scanners', 'queue_scanners', (ungettext( 'Flagged By Scanners ({0})', 'Flagged By Scanners ({0})', counts['scanners'] ) .format(counts['scanners'])), )) tabnav.append(( 'mad', 'queue_mad', (ungettext( 'Flagged for Human Review ({0})', 'Flagged for Human Review ({0})', counts['mad'] ).format(counts['mad'])), )) if acl.action_allowed(request, amo.permissions.STATIC_THEMES_REVIEW): new_text = ugettext('New ({0})') update_text = ungettext( 'Update ({0})', 'Updates ({0})', counts['theme_pending']) tabnav.extend(( ('theme_nominated', 'queue_theme_nominated', '🎨 ' + new_text.format(counts['theme_nominated'])), ('theme_pending', 'queue_theme_pending', '🎨 ' + update_text.format(counts['theme_pending'])), )) if acl.action_allowed(request, amo.permissions.RATINGS_MODERATE): tabnav.append( ('moderated', 'queue_moderated', (ungettext('Rating Review ({0})', 'Rating Reviews ({0})', counts['moderated']) .format(counts['moderated']))), ) if acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW): tabnav.append( ('auto_approved', 'queue_auto_approved', (ungettext('Auto Approved ({0})', 'Auto Approved ({0})', counts['auto_approved']) .format(counts['auto_approved']))), ) if acl.action_allowed(request, amo.permissions.ADDONS_CONTENT_REVIEW): tabnav.append( ('content_review', 'queue_content_review', (ungettext('Content Review ({0})', 'Content Review ({0})', counts['content_review']) .format(counts['content_review']))), ) if acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN): tabnav.append( ('expired_info_requests', 'queue_expired_info_requests', (ungettext('Expired Info Request ({0})', 'Expired Info Requests ({0})', counts['expired_info_requests']) .format(counts['expired_info_requests']))), ) tabnav.append( ('pending_rejection', 'queue_pending_rejection', (ungettext('Pending Rejection ({0})', 'Pending Rejection ({0})', counts['pending_rejection']) .format(counts['pending_rejection']))), ) else: tabnav = [ ('all', 'unlisted_queue_all', ugettext('All Unlisted Add-ons')) ] return tabnav
def can_view_stats(self, request): if request and request.user: return (self.owned_by(request.user) or acl.action_allowed( request, amo.permissions.COLLECTION_STATS_VIEW)) return False
def global_settings(request): """ Storing standard AMO-wide information used in global headers, such as account links and settings. """ account_links = [] tools_links = [] context = {} tools_title = gettext('Tools') is_reviewer = False # We're using `getattr` here because `request.user` can be missing, # e.g in case of a 500-server error. if getattr(request, 'user', AnonymousUser()).is_authenticated: is_reviewer = acl.is_user_any_kind_of_reviewer(request.user) account_links.append({ 'text': gettext('My Profile'), 'href': request.user.get_url_path() }) account_links.append({ 'text': gettext('Account Settings'), 'href': reverse('users.edit') }) account_links.append({ 'text': gettext('My Collections'), 'href': reverse('collections.list') }) account_links.append({ 'text': gettext('Log out'), 'href': reverse('devhub.logout') + '?to=' + quote(request.path), }) if request.user.is_developer: tools_links.append({ 'text': gettext('Manage My Submissions'), 'href': reverse('devhub.addons'), }) tools_links.append({ 'text': gettext('Submit a New Add-on'), 'href': reverse('devhub.submit.agreement'), }) tools_links.append({ 'text': gettext('Submit a New Theme'), 'href': reverse('devhub.submit.agreement'), }) tools_links.append({ 'text': gettext('Developer Hub'), 'href': reverse('devhub.index') }) tools_links.append({ 'text': gettext('Manage API Keys'), 'href': reverse('devhub.api_key') }) if is_reviewer: tools_links.append({ 'text': gettext('Reviewer Tools'), 'href': reverse('reviewers.dashboard'), }) if acl.action_allowed(request, amo.permissions.ANY_ADMIN): tools_links.append({ 'text': gettext('Admin Tools'), 'href': reverse('admin:index') }) context['user'] = request.user else: context['user'] = AnonymousUser() context.update({ 'account_links': account_links, 'settings': settings, 'amo': amo, 'tools_links': tools_links, 'tools_title': tools_title, 'is_reviewer': is_reviewer, }) return context
def get_actions(self, request, addon): actions = SortedDict() if request is None: # If request is not set, it means we are just (ab)using the # ReviewHelper for its `handler` attribute and we don't care about # the actions. return actions labels, details = self._review_actions() reviewable_because_complete = addon.status not in (amo.STATUS_NULL, amo.STATUS_DELETED) reviewable_because_admin = (not addon.admin_review or acl.action_allowed( request, 'ReviewerAdminTools', 'View')) reviewable_because_submission_time = ( not is_limited_reviewer(request) or (addon.latest_version is not None and addon.latest_version.nomination is not None and (datetime.datetime.now() - addon.latest_version.nomination >= datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS)))) reviewable_because_pending = addon.latest_version is not None and len( addon.latest_version.is_unreviewed) > 0 if (reviewable_because_complete and reviewable_because_admin and reviewable_because_submission_time and reviewable_because_pending): if self.review_type != 'preliminary': if addon.is_listed: label = _lazy('Push to public') else: label = _lazy('Grant full review') actions['public'] = { 'method': self.handler.process_public, 'minimal': False, 'label': label } # An unlisted sideload add-on, which requests a full review, cannot # be granted a preliminary review. if addon.is_listed or self.review_type == 'preliminary': actions['prelim'] = { 'method': self.handler.process_preliminary, 'label': labels['prelim'], 'minimal': False } actions['reject'] = { 'method': self.handler.process_sandbox, 'label': _lazy('Reject'), 'minimal': False } actions['info'] = { 'method': self.handler.request_information, 'label': _lazy('Request more information'), 'minimal': True } actions['super'] = { 'method': self.handler.process_super_review, 'label': _lazy('Request super-review'), 'minimal': True } actions['comment'] = { 'method': self.handler.process_comment, 'label': _lazy('Comment'), 'minimal': True } for k, v in actions.items(): v['details'] = details.get(k) return actions
def queue_tabnav(context): """Returns tuple of tab navigation for the queue pages. Each tuple contains three elements: (tab_code, page_url, tab_text) """ request = context['request'] listed = not context.get('unlisted') if listed: tabnav = [] if acl.action_allowed(request, amo.permissions.ADDONS_RECOMMENDED_REVIEW): tabnav.append( ('recommended', 'queue_recommended', gettext('Recommended'))) if acl.action_allowed(request, amo.permissions.ADDONS_REVIEW): tabnav.append(( 'extension', 'queue_extension', '🛠️ ' + gettext('Other Pending Review'), )) tabnav.append( ('scanners', 'queue_scanners', gettext('Flagged By Scanners'))) tabnav.append( ('mad', 'queue_mad', gettext('Flagged for Human Review'))) if acl.action_allowed(request, amo.permissions.STATIC_THEMES_REVIEW): tabnav.extend(( ( 'theme_nominated', 'queue_theme_nominated', '🎨 ' + gettext('New'), ), ( 'theme_pending', 'queue_theme_pending', '🎨 ' + gettext('Updates'), ), )) if acl.action_allowed(request, amo.permissions.RATINGS_MODERATE): tabnav.append( ('moderated', 'queue_moderated', gettext('Rating Reviews'))) if acl.action_allowed(request, amo.permissions.ADDONS_REVIEW): tabnav.append(('auto_approved', 'queue_auto_approved', gettext('Auto Approved'))) if acl.action_allowed(request, amo.permissions.ADDONS_CONTENT_REVIEW): tabnav.append(('content_review', 'queue_content_review', gettext('Content Review'))) if acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN): tabnav.append(( 'pending_rejection', 'queue_pending_rejection', gettext('Pending Rejection'), )) else: tabnav = [ ('all', 'unlisted_queue_all', gettext('All Unlisted Add-ons')), ( 'pending_manual_approval', 'unlisted_queue_pending_manual_approval', gettext('Unlisted Add-ons Pending Manual Approval'), ), ] return tabnav
def _get_themes(request, reviewer, flagged=False, rereview=False): """Check out themes. :param flagged: Flagged themes (amo.STATUS_REVIEW_PENDING) :param rereview: Re-uploaded themes (RereviewQueueTheme) """ num = 0 themes = [] locks = [] status = (amo.STATUS_REVIEW_PENDING if flagged else amo.STATUS_PUBLIC if rereview else amo.STATUS_PENDING) if rereview: # Rereview themes. num, themes, locks = _get_rereview_themes(reviewer) else: # Pending and flagged themes. locks = ThemeLock.objects.no_cache().filter( reviewer=reviewer, theme__addon__status=status) num, themes = _calc_num_themes_checkout(locks) if themes: return themes themes = Persona.objects.no_cache().filter(addon__status=status, themelock=None) # Don't allow self-reviews. if (not settings.ALLOW_SELF_REVIEWS and not acl.action_allowed(request, amo.permissions.ADMIN)): if rereview: themes = themes.exclude(theme__addon__addonuser__user=reviewer) else: themes = themes.exclude(addon__addonuser__user=reviewer) # Check out themes by setting lock. themes = list(themes)[:num] expiry = get_updated_expiry() for theme in themes: ThemeLock.objects.create(theme=_rereview_to_theme(rereview, theme), reviewer=reviewer, expiry=expiry) # Empty pool? Go look for some expired locks. if not themes: expired_locks = ThemeLock.objects.filter( expiry__lte=datetime.datetime.now(), theme__addon__status=status)[:rvw.THEME_INITIAL_LOCKS] # Steal expired locks. for lock in expired_locks: lock.reviewer = reviewer lock.expiry = expiry lock.save() if expired_locks: locks = expired_locks if rereview: return (RereviewQueueTheme.objects.no_cache().filter( theme__themelock__reviewer=reviewer).exclude( theme__addon__status=amo.STATUS_REJECTED)) # New theme locks may have been created, grab all reviewer's themes again. return [lock.theme for lock in locks]
def is_admin_reviewer(request): return acl.action_allowed(request, 'ReviewerAdminTools', 'View')
def wrapper(request, *args, **kw): view_on_get = _view_on_get(request) if view_on_get or acl.action_allowed(request, permission): return f(request, *args, **kw) else: raise PermissionDenied
def themes_single(request, slug): """ Like a detail page, manually review a single theme if it is pending and isn't locked. """ reviewer = request.user reviewable = True # Don't review an already reviewed theme. theme = get_object_or_404(Persona, addon__slug=slug) if (theme.addon.status != amo.STATUS_PENDING and not theme.rereviewqueuetheme_set.all()): reviewable = False if (not settings.ALLOW_SELF_REVIEWS and not acl.action_allowed(request, amo.permissions.ADMIN) and theme.addon.has_author(request.user)): reviewable = False else: # Don't review a locked theme (that's not locked to self). try: lock = theme.themelock if (lock.reviewer.id != reviewer.id and lock.expiry > datetime.datetime.now()): reviewable = False elif (lock.reviewer.id != reviewer.id and lock.expiry < datetime.datetime.now()): # Steal expired lock. lock.reviewer = reviewer lock.expiry = get_updated_expiry() lock.save() else: # Update expiry. lock.expiry = get_updated_expiry() lock.save() except ThemeLock.DoesNotExist: # Create lock if not created. ThemeLock.objects.create(theme=theme, reviewer=reviewer, expiry=get_updated_expiry()) ThemeReviewFormset = formset_factory(forms.ThemeReviewForm) formset = ThemeReviewFormset(initial=[{'theme': theme.id}]) # Since we started the review on the single page, we want to return to the # single page rather than get shot back to the queue. request.session['theme_redirect_url'] = reverse('editors.themes.single', args=[theme.addon.slug]) rereview = (theme.rereviewqueuetheme_set.all()[0] if theme.rereviewqueuetheme_set.exists() else None) return render( request, 'editors/themes/single.html', context( **{ 'formset': formset, 'theme': rereview if rereview else theme, 'theme_formsets': zip([rereview if rereview else theme], formset), 'theme_reviews': paginate( request, ActivityLog.objects.filter( action=amo.LOG.THEME_REVIEW.id, _arguments__contains=theme.addon.id)), 'actions': get_actions_json(), 'theme_count': 1, 'rereview': rereview, 'reviewable': reviewable, 'reject_reasons': rvw.THEME_REJECT_REASONS, 'action_dict': rvw.REVIEW_ACTIONS, 'tab': ('flagged' if theme.addon.status == amo.STATUS_REVIEW_PENDING else 'rereview' if rereview else 'pending') }))
def wrapper(request, *args, **kw): if _view_on_get(request) or acl.action_allowed( request, permissions.RATINGS_MODERATE): return f(request, *args, **kw) raise PermissionDenied
def get_actions(self, request): actions = OrderedDict() if request is None: # If request is not set, it means we are just (ab)using the # ReviewHelper for its `handler` attribute and we don't care about # the actions. return actions # Conditions used below. is_post_reviewer = acl.action_allowed( request, amo.permissions.ADDONS_POST_REVIEW) is_unlisted_reviewer = acl.action_allowed( request, amo.permissions.ADDONS_REVIEW_UNLISTED) is_content_reviewer = acl.action_allowed( request, amo.permissions.ADDONS_CONTENT_REVIEW) is_admin_tools_viewer = acl.action_allowed( request, amo.permissions.REVIEWS_ADMIN) reviewable_because_complete = self.addon.status not in ( amo.STATUS_NULL, amo.STATUS_DELETED) regular_addon_review_is_allowed = ( not self.content_review_only and not self.addon.needs_admin_code_review and not self.addon.needs_admin_content_review and not self.addon.needs_admin_theme_review) regular_content_review_is_allowed = ( self.content_review_only and not self.addon.needs_admin_content_review and (not self.addon.needs_admin_code_review or self.version.source)) reviewable_because_not_reserved_for_admins_or_user_is_admin = ( is_admin_tools_viewer or (self.version and (regular_addon_review_is_allowed or regular_content_review_is_allowed))) reviewable_because_submission_time = ( not is_limited_reviewer(request) or (self.version and self.version.nomination is not None and (datetime.now() - self.version.nomination >= timedelta(hours=amo.REVIEW_LIMITED_DELAY_HOURS)))) reviewable_because_pending = (self.version and len(self.version.is_unreviewed) > 0) # Note: approval/content confirmation do not care about self.version, # only self.addon.current_version. This allows reviewers to approve # add-ons even when their latest submitted version is disabled for some # reason. was_auto_approved_and_user_can_post_review = ( self.addon.current_version and self.addon.current_version.was_auto_approved and is_post_reviewer) was_auto_approved_and_user_can_content_review = ( self.addon.current_version and self.addon.current_version.was_auto_approved and is_content_reviewer and self.content_review_only) is_unlisted_and_user_can_review_unlisted = ( self.version and self.version.channel == amo.RELEASE_CHANNEL_UNLISTED and is_unlisted_reviewer) is_public_and_listed_and_user_can_post_review = ( self.version and self.addon.status == amo.STATUS_PUBLIC and self.version.channel == amo.RELEASE_CHANNEL_LISTED and is_post_reviewer) is_public_and_listed_and_user_can_content_review = ( self.version and self.addon.status == amo.STATUS_PUBLIC and self.version.channel == amo.RELEASE_CHANNEL_LISTED and is_content_reviewer and self.content_review_only) # Definitions for all actions. actions['public'] = { 'method': self.handler.process_public, 'minimal': False, 'details': _('This will approve, sign, and publish this ' 'version. The comments will be sent to the ' 'developer.'), 'label': _('Approve'), 'available': (reviewable_because_complete and reviewable_because_not_reserved_for_admins_or_user_is_admin and reviewable_because_submission_time and reviewable_because_pending and not self.content_review_only) } actions['reject'] = { 'method': self.handler.process_sandbox, 'label': _('Reject'), 'details': _('This will reject this version and remove it ' 'from the queue. The comments will be sent ' 'to the developer.'), 'minimal': False, 'available': actions['public']['available'], } actions['confirm_auto_approved'] = { 'method': self.handler.confirm_auto_approved, 'label': _('Confirm Approval'), 'details': _('The latest public version of this add-on was ' 'automatically approved. This records your ' 'confirmation of the approval of that version, ' 'without notifying the developer.'), 'minimal': True, 'comments': False, 'available': (reviewable_because_not_reserved_for_admins_or_user_is_admin and (was_auto_approved_and_user_can_post_review or was_auto_approved_and_user_can_content_review or is_unlisted_and_user_can_review_unlisted)) } actions['reject_multiple_versions'] = { 'method': self.handler.reject_multiple_versions, 'label': _('Reject Multiple Versions'), 'minimal': True, 'versions': True, 'details': _('This will reject the selected public ' 'versions. The comments will be sent to the ' 'developer.'), 'available': (self.addon.type != amo.ADDON_STATICTHEME and reviewable_because_not_reserved_for_admins_or_user_is_admin and (is_public_and_listed_and_user_can_post_review or is_public_and_listed_and_user_can_content_review)) } actions['reply'] = { 'method': self.handler.reviewer_reply, 'label': _('Reviewer reply'), 'details': _('This will send a message to the developer. ' 'You will be notified when they reply.'), 'minimal': True, 'available': self.version is not None, } actions['super'] = { 'method': self.handler.process_super_review, 'label': _('Request super-review'), 'details': _('If you have concerns about this add-on that ' 'an admin reviewer should look into, enter ' 'your comments in the area below. They will ' 'not be sent to the developer.'), 'minimal': True, 'available': self.version is not None, } actions['comment'] = { 'method': self.handler.process_comment, 'label': _('Comment'), 'details': _('Make a comment on this version. The developer ' 'won\'t be able to see this.'), 'minimal': True, 'available': True, } # Small tweaks to labels and descriptions when in content review mode. if self.content_review_only: if 'confirm_auto_approved' in actions: actions['confirm_auto_approved'].update({ 'label': _('Approve Content'), 'details': _('This records your approbation of the ' 'content of the latest public version, ' 'without notifying the developer.'), }) return OrderedDict(((key, action) for key, action in actions.items() if action['available']))
def wrapper(request, *args, **kw): from olympia.access import acl for permission in permissions: if acl.action_allowed(request, permission): return f(request, *args, **kw) raise PermissionDenied
def get_actions(self, request): actions = SortedDict() if request is None: # If request is not set, it means we are just (ab)using the # ReviewHelper for its `handler` attribute and we don't care about # the actions. return actions reviewable_because_complete = self.addon.status not in ( amo.STATUS_NULL, amo.STATUS_DELETED) reviewable_because_admin = ( not self.addon.admin_review or acl.action_allowed( request, amo.permissions.REVIEWER_ADMIN_TOOLS_VIEW)) reviewable_because_submission_time = ( not is_limited_reviewer(request) or (self.version is not None and self.version.nomination is not None and (datetime.datetime.now() - self.version.nomination >= datetime.timedelta(hours=REVIEW_LIMITED_DELAY_HOURS)))) reviewable_because_pending = (self.version is not None and len(self.version.is_unreviewed) > 0) if (reviewable_because_complete and reviewable_because_admin and reviewable_because_submission_time and reviewable_because_pending): actions['public'] = { 'method': self.handler.process_public, 'minimal': False, 'details': _('This will approve, sign, and publish this ' 'version. The comments will be sent to the ' 'developer.'), 'label': _('Approve') } actions['reject'] = { 'method': self.handler.process_sandbox, 'label': _('Reject'), 'details': _('This will reject this version and remove it ' 'from the queue. The comments will be sent ' 'to the developer.'), 'minimal': False } if acl.action_allowed(request, amo.permissions.ADDONS_POST_REVIEW): # Post-reviewers have 2 extra actions depending on the state of # the add-on: # If the addon current version was auto-approved, they can confirm # the approval. if (self.addon.current_version and self.addon.current_version.was_auto_approved): actions['confirm_auto_approved'] = { 'method': self.handler.confirm_auto_approved, 'label': _('Confirm Approval'), 'details': _('The latest public version of this add-on ' 'was automatically approved. This records ' 'your confirmation of the approval, ' 'without notifying the developer.'), 'minimal': True, 'comments': False, } # In any case, they can reject multiple versions in one action. actions['reject_multiple_versions'] = { 'method': self.handler.reject_multiple_versions, 'label': _('Reject Multiple Versions'), 'minimal': True, 'versions': True, 'details': _('This will reject the selected versions. ' 'The comments will be sent to the developer.'), } if self.version: actions['info'] = { 'method': self.handler.request_information, 'label': _('Reviewer reply'), 'details': _('This will send a message to the developer. ' 'You will be notified when they reply.'), 'minimal': True } actions['super'] = { 'method': self.handler.process_super_review, 'label': _('Request super-review'), 'details': _('If you have concerns about this add-on that ' 'an admin reviewer should look into, enter ' 'your comments in the area below. They will ' 'not be sent to the developer.'), 'minimal': True } actions['comment'] = { 'method': self.handler.process_comment, 'label': _('Comment'), 'details': _('Make a comment on this version. The developer ' 'won\'t be able to see this.'), 'minimal': True } return actions
def wrapper(request, *args, **kw): if (_view_on_get(request) or acl.action_allowed(request, permissions.ADDONS_REVIEW) or acl.check_static_theme_reviewer(request)): return f(request, *args, **kw) raise PermissionDenied
def performance(request, user_id=False): user = request.user editors = _recent_editors() is_admin = (acl.action_allowed(request, 'Admin', '%') or acl.action_allowed(request, 'ReviewerAdminTools', 'View')) if is_admin and user_id: try: user = UserProfile.objects.get(pk=user_id) except UserProfile.DoesNotExist: pass # Use request.user from above. motd_editable = acl.action_allowed(request, 'AddonReviewerMOTD', 'Edit') monthly_data = _performance_by_month(user.id) performance_total = _performance_total(monthly_data) # Incentive point breakdown. today = date.today() month_ago = today - timedelta(days=30) year_ago = today - timedelta(days=365) point_total = ReviewerScore.get_total(user) totals = ReviewerScore.get_breakdown(user) months = ReviewerScore.get_breakdown_since(user, month_ago) years = ReviewerScore.get_breakdown_since(user, year_ago) def _sum(iter, types, exclude=False): """Sum the `total` property for items in `iter` that have an `atype` that is included in `types` when `exclude` is False (default) or not in `types` when `exclude` is True.""" return sum(s.total for s in iter if (s.atype in types) == (not exclude)) breakdown = { 'month': { 'addons': _sum(months, amo.GROUP_TYPE_ADDON), 'themes': _sum(months, amo.GROUP_TYPE_THEME), 'other': _sum(months, amo.GROUP_TYPE_ADDON + amo.GROUP_TYPE_THEME, exclude=True) }, 'year': { 'addons': _sum(years, amo.GROUP_TYPE_ADDON), 'themes': _sum(years, amo.GROUP_TYPE_THEME), 'other': _sum(years, amo.GROUP_TYPE_ADDON + amo.GROUP_TYPE_THEME, exclude=True) }, 'total': { 'addons': _sum(totals, amo.GROUP_TYPE_ADDON), 'themes': _sum(totals, amo.GROUP_TYPE_THEME), 'other': _sum(totals, amo.GROUP_TYPE_ADDON + amo.GROUP_TYPE_THEME, exclude=True) } } data = context(request, monthly_data=json.dumps(monthly_data), performance_month=performance_total['month'], performance_year=performance_total['year'], breakdown=breakdown, point_total=point_total, editors=editors, current_user=user, is_admin=is_admin, is_user=(request.user.id == user.id), motd_editable=motd_editable) return render(request, 'editors/performance.html', data)
def has_permission(self, request, view): return ((request.method in SAFE_METHODS and acl.action_allowed(request, 'ReviewerTools', 'View')) or acl.check_addons_reviewer(request))
def review(request, addon, channel=None): whiteboard_url = reverse('reviewers.whiteboard', args=(channel or 'listed', addon.slug if addon.slug else addon.pk)) channel, content_review_only = determine_channel(channel) was_auto_approved = (channel == amo.RELEASE_CHANNEL_LISTED and addon.current_version and addon.current_version.was_auto_approved) is_static_theme = addon.type == amo.ADDON_STATICTHEME # If we're just looking (GET) we can bypass the specific permissions checks # if we have ReviewerTools:View. bypass_more_specific_permissions_because_read_only = ( request.method == 'GET' and acl.action_allowed(request, amo.permissions.REVIEWER_TOOLS_VIEW)) if not bypass_more_specific_permissions_because_read_only: perform_review_permission_checks( request, addon, channel, content_review_only=content_review_only) version = addon.find_latest_version(channel=channel, exclude=(amo.STATUS_BETA, )) if not settings.ALLOW_SELF_REVIEWS and addon.has_author(request.user): amo.messages.warning(request, ugettext('Self-reviews are not allowed.')) return redirect(reverse('reviewers.queue')) # Get the current info request state to set as the default. form_initial = {'info_request': addon.pending_info_request} form_helper = ReviewHelper(request=request, addon=addon, version=version, content_review_only=content_review_only) form = ReviewForm(request.POST if request.method == 'POST' else None, helper=form_helper, initial=form_initial) is_admin = acl.action_allowed(request, amo.permissions.REVIEWS_ADMIN) approvals_info = None reports = None user_ratings = None if channel == amo.RELEASE_CHANNEL_LISTED: if was_auto_approved: try: approvals_info = addon.addonapprovalscounter except AddonApprovalsCounter.DoesNotExist: pass developers = addon.listed_authors reports = Paginator((AbuseReport.objects.filter( Q(addon=addon) | Q(user__in=developers)).order_by('-created')), 5).page(1) user_ratings = Paginator((Rating.without_replies.filter( addon=addon, rating__lte=3, body__isnull=False).order_by('-created')), 5).page(1) if content_review_only: queue_type = 'content_review' elif was_auto_approved: queue_type = 'auto_approved' else: queue_type = form.helper.handler.review_type redirect_url = reverse('reviewers.queue_%s' % queue_type) else: redirect_url = reverse('reviewers.unlisted_queue_all') if request.method == 'POST' and form.is_valid(): form.helper.process() amo.messages.success(request, ugettext('Review successfully processed.')) clear_reviewing_cache(addon.id) return redirect(redirect_url) # Kick off validation tasks for any files in this version which don't have # cached validation, since reviewers will almost certainly need to access # them. But only if we're not running in eager mode, since that could mean # blocking page load for several minutes. if version and not getattr(settings, 'CELERY_ALWAYS_EAGER', False): for file_ in version.all_files: if not file_.has_been_validated: devhub_tasks.validate(file_) actions = form.helper.actions.items() try: # Find the previously approved version to compare to. show_diff = version and (addon.versions.exclude(id=version.id).filter( # We're looking for a version that was either manually approved # (either it has no auto approval summary, or it has one but # with a negative verdict because it was locked by a reviewer # who then approved it themselves), or auto-approved but then # confirmed. Q(autoapprovalsummary__isnull=True) | Q(autoapprovalsummary__verdict=amo.NOT_AUTO_APPROVED) | Q(autoapprovalsummary__verdict=amo.AUTO_APPROVED, autoapprovalsummary__confirmed=True)).filter( channel=channel, files__isnull=False, created__lt=version.created, files__status=amo.STATUS_PUBLIC).latest()) except Version.DoesNotExist: show_diff = None # The actions we shouldn't show a minimal form for. actions_full = [ k for (k, a) in actions if not (is_static_theme or a.get('minimal')) ] # The actions we should show the comments form for (contrary to minimal # form above, it defaults to True, because most actions do need to have # the comments form). actions_comments = [k for (k, a) in actions if a.get('comments', True)] versions = (Version.unfiltered.filter( addon=addon, channel=channel).select_related('autoapprovalsummary').exclude( files__status=amo.STATUS_BETA).order_by('-created').transform( Version.transformer_activity).transform(Version.transformer)) # We assume comments on old deleted versions are for listed versions. # See _get_comments_for_hard_deleted_versions above for more detail. all_versions = (_get_comments_for_hard_deleted_versions(addon) if channel == amo.RELEASE_CHANNEL_LISTED else []) all_versions.extend(versions) all_versions.sort(key=lambda v: v.created, reverse=True) pager = amo.utils.paginate(request, all_versions, 10) num_pages = pager.paginator.num_pages count = pager.paginator.count auto_approval_info = {} # Now that we've paginated the versions queryset, iterate on them to # generate auto approvals info. Note that the variable should not clash # the already existing 'version'. for a_version in pager.object_list: if not a_version.is_ready_for_auto_approval: continue try: summary = a_version.autoapprovalsummary except AutoApprovalSummary.DoesNotExist: auto_approval_info[a_version.pk] = None continue # Call calculate_verdict() again, it will use the data already stored. verdict_info = summary.calculate_verdict(pretty=True) auto_approval_info[a_version.pk] = verdict_info flags = get_flags(addon, version) if version else [] if not is_static_theme: try: whiteboard = Whiteboard.objects.get(pk=addon.pk) except Whiteboard.DoesNotExist: whiteboard = Whiteboard(pk=addon.pk) whiteboard_form = WhiteboardForm(instance=whiteboard, prefix='whiteboard') else: whiteboard_form = None backgrounds = version.get_background_image_urls() if version else [] user_changes_actions = [ amo.LOG.ADD_USER_WITH_ROLE.id, amo.LOG.CHANGE_USER_WITH_ROLE.id, amo.LOG.REMOVE_USER_WITH_ROLE.id ] user_changes_log = AddonLog.objects.filter( activity_log__action__in=user_changes_actions, addon=addon).order_by('id') ctx = context(request, actions=actions, actions_comments=actions_comments, actions_full=actions_full, addon=addon, api_token=request.COOKIES.get(API_TOKEN_COOKIE, None), approvals_info=approvals_info, auto_approval_info=auto_approval_info, backgrounds=backgrounds, content_review_only=content_review_only, count=count, flags=flags, form=form, is_admin=is_admin, num_pages=num_pages, pager=pager, reports=reports, show_diff=show_diff, subscribed=ReviewerSubscription.objects.filter( user=request.user, addon=addon).exists(), unlisted=(channel == amo.RELEASE_CHANNEL_UNLISTED), user_changes=user_changes_log, user_ratings=user_ratings, version=version, was_auto_approved=was_auto_approved, whiteboard_form=whiteboard_form, whiteboard_url=whiteboard_url) return render(request, 'reviewers/review.html', ctx)