def wrapper(request, addon, *args, **kw): if theme: kw['theme'] = addon.is_persona() elif addon.is_persona(): # Don't allow theme views if theme not passed in. raise http.Http404 def fun(): return f(request, addon_id=addon.id, addon=addon, *args, **kw) if allow_reviewers: if acl.is_reviewer(request, addon): return fun() # Require an owner or dev for POST requests. if request.method == 'POST': if acl.check_addon_ownership(request, addon, dev=not owner_for_post): return fun() # Ignore disabled so they can view their add-on. elif acl.check_addon_ownership(request, addon, dev=True, ignore_disabled=True): # Redirect to the submit flow if they're not done. if (not submitting and addon.should_redirect_to_submit_flow()): return redirect('devhub.submit.details', addon.slug) return fun() raise PermissionDenied
def wrapper(request, addon, *args, **kw): def fun(): return f(request, addon_id=addon.id, addon=addon, *args, **kw) if request.method in ('HEAD', 'GET'): # Allow reviewers for read operations. if allow_reviewers_for_read and (acl.is_reviewer( request, addon) or acl.action_allowed( request, permissions.REVIEWER_TOOLS_VIEW)): return fun() # On read-only requests, ignore disabled so developers can # still view their add-on. if acl.check_addon_ownership(request, addon, dev=not owner_for_get, ignore_disabled=True): # Redirect to the submit flow if they're not done. if (not submitting and addon.should_redirect_to_submit_flow()): return redirect('devhub.submit.details', addon.slug) return fun() # Require an owner or dev for POST requests (if the add-on status # is disabled that check will return False). elif request.method == 'POST': if acl.check_addon_ownership(request, addon, dev=not owner_for_post): return fun() raise PermissionDenied
def setup_viewer(request, file_obj): addon = file_obj.version.addon data = { 'file': file_obj, 'version': file_obj.version, 'addon': addon, 'status': False, 'selected': {}, 'validate_url': '' } is_user_a_reviewer = acl.is_reviewer(request, addon) if (is_user_a_reviewer or acl.check_addon_ownership( request, addon, dev=True, ignore_disabled=True)): data['validate_url'] = reverse('devhub.json_file_validation', args=[addon.slug, file_obj.id]) data['automated_signing'] = file_obj.automated_signing if file_obj.has_been_validated: data['validation_data'] = file_obj.validation.processed_validation if is_user_a_reviewer: data['file_link'] = { 'text': ugettext('Back to review'), 'url': reverse('reviewers.review', args=[addon.slug]) } else: data['file_link'] = { 'text': ugettext('Back to add-on'), 'url': reverse('addons.detail', args=[addon.pk]) } return data
def wrapper(request, addon, *args, **kw): if theme: kw['theme'] = addon.is_persona() elif addon.is_persona(): # Don't allow theme views if theme not passed in. raise http.Http404 def fun(): return f(request, addon_id=addon.id, addon=addon, *args, **kw) if allow_reviewers: if acl.is_reviewer(request, addon): return fun() # Require an owner or dev for POST requests. if request.method == 'POST': if acl.check_addon_ownership(request, addon, dev=not owner_for_post): return fun() # Ignore disabled so they can view their add-on. elif acl.check_addon_ownership(request, addon, viewer=True, ignore_disabled=True): # Redirect to the submit flow if they're not done. if (not submitting and addon.should_redirect_to_submit_flow()): return redirect('devhub.submit.details', addon.slug) return fun() raise PermissionDenied
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( include_deleted=True) and acl.is_reviewer(request, obj) return can_access_because_viewer or can_access_because_listed_reviewer
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 review_list(request, addon, review_id=None, user_id=None): qs = Review.without_replies.all().filter(addon=addon).order_by('-created') ctx = {'addon': addon, 'grouped_ratings': GroupedRating.get(addon.id)} ctx['form'] = forms.ReviewForm(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(Review.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'] = Review.get_replies(reviews.object_list) if request.user.is_authenticated(): ctx['review_perms'] = { 'is_admin': is_admin, 'is_reviewer': acl.is_reviewer(request, addon), '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, 'reviews/review_list.html', ctx)
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, but only if they aren't listed as an author of the add-on and the add-on is flagged for moderation * Users in a group with "Users:Edit" privileges. * Users in a group with "Addons:Edit" privileges. Persona reviewers can't delete addons reviews. """ is_author = review.addon.has_author(request.user) return ( review.user_id == request.user.id or not is_author and ( (acl.is_reviewer(request, review.addon) and review.editorreview) or acl.action_allowed(request, amo.permissions.USERS_EDIT) or acl.action_allowed(request, amo.permissions.ADDONS_EDIT)))
def allowed(request, file): try: version = file.version addon = version.addon except ObjectDoesNotExist: raise http.Http404 # General case: addon is listed. if version.channel == amo.RELEASE_CHANNEL_LISTED: # We don't show the file-browser publicly because of potential DOS # issues, we're working on a fix but for now, let's not do this. # (cgrebs, 06042017) is_owner = acl.check_addon_ownership(request, addon, dev=True) if (acl.is_reviewer(request, addon) or is_owner): return True # Public and sources are visible, or reviewer. raise PermissionDenied # Listed but not allowed. # Not listed? Needs an owner or an "unlisted" admin. else: if owner_or_unlisted_reviewer(request, addon): return True raise http.Http404 # Not listed, not owner or admin.
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 # 2 kind of checks are made for the review page. # - Base permission checks to access the review page itself, done in # the review() view # - A more specific check for each action, done below, restricting # their availability while not affecting whether the user can see # the review page or not. permission = None version_is_unlisted = (self.version and self.version.channel == amo.RELEASE_CHANNEL_UNLISTED) try: is_recommendable = self.addon.discoveryitem.recommendable except DiscoveryItem.DoesNotExist: is_recommendable = False current_version_is_listed_and_auto_approved = ( self.version and self.version.channel == amo.RELEASE_CHANNEL_LISTED and self.addon.current_version and self.addon.current_version.was_auto_approved) if is_recommendable: is_admin_needed = (self.addon.needs_admin_content_review or self.addon.needs_admin_code_review) permission = amo.permissions.ADDONS_RECOMMENDED_REVIEW elif self.content_review: is_admin_needed = self.addon.needs_admin_content_review permission = amo.permissions.ADDONS_CONTENT_REVIEW elif version_is_unlisted: is_admin_needed = self.addon.needs_admin_code_review permission = amo.permissions.ADDONS_REVIEW_UNLISTED elif self.addon.type == amo.ADDON_STATICTHEME: is_admin_needed = self.addon.needs_admin_theme_review permission = amo.permissions.STATIC_THEMES_REVIEW elif current_version_is_listed_and_auto_approved: is_admin_needed = (self.addon.needs_admin_content_review or self.addon.needs_admin_code_review) permission = amo.permissions.ADDONS_POST_REVIEW else: is_admin_needed = (self.addon.needs_admin_content_review or self.addon.needs_admin_code_review) permission = amo.permissions.ADDONS_REVIEW assert permission is not None if is_admin_needed: permission = amo.permissions.REVIEWS_ADMIN # Is the current user a reviewer for this kind of add-on ? is_reviewer = acl.is_reviewer(request, self.addon) # Is the current user an appropriate reviewer, noy only for this kind # of add-on, but also for the state the add-on is in ? (Allows more # impactful actions). is_appropriate_reviewer = acl.action_allowed_user( request.user, permission) # Special logic for availability of reject multiple action: if (self.content_review or is_recommendable or self.addon.type == amo.ADDON_STATICTHEME): can_reject_multiple = is_appropriate_reviewer else: # When doing a code review, this action is also available to # users with Addons:PostReview even if the current version hasn't # been auto-approved, provided that the add-on isn't marked as # needing admin review. can_reject_multiple = ( is_appropriate_reviewer or (acl.action_allowed_user( request.user, amo.permissions.ADDONS_POST_REVIEW) and not is_admin_needed)) addon_is_complete = self.addon.status not in (amo.STATUS_NULL, amo.STATUS_DELETED) version_is_unreviewed = self.version and self.version.is_unreviewed addon_is_valid = self.addon.is_public() or self.addon.is_unreviewed() addon_is_valid_and_version_is_listed = (addon_is_valid and self.version and self.version.channel == amo.RELEASE_CHANNEL_LISTED) # 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': (not self.content_review and addon_is_complete and version_is_unreviewed and is_appropriate_reviewer) } 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': (not self.content_review and addon_is_complete and version_is_unreviewed and is_appropriate_reviewer) } actions['approve_content'] = { 'method': self.handler.approve_content, 'label': _('Approve Content'), 'details': _('This records your approbation of the ' 'content of the latest public version, ' 'without notifying the developer.'), 'minimal': False, 'comments': False, 'available': (self.content_review and addon_is_valid_and_version_is_listed and is_appropriate_reviewer), } 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': (not self.content_review and addon_is_valid_and_version_is_listed and current_version_is_listed_and_auto_approved and is_appropriate_reviewer), } 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': (addon_is_valid_and_version_is_listed and can_reject_multiple), } actions['block_multiple_versions'] = { 'method': self.handler.reject_multiple_versions, 'label': _('Block Multiple Versions'), 'minimal': True, 'versions': True, 'comments': False, 'details': _('This will disable the selected approved ' 'versions silently, and open up the block creation ' 'admin page.'), 'available': (self.addon.type != amo.ADDON_STATICTHEME and version_is_unlisted and is_appropriate_reviewer), } actions['confirm_multiple_versions'] = { 'method': self.handler.confirm_multiple_versions, 'label': _('Confirm Multiple Versions'), 'minimal': True, 'versions': True, 'details': _('This will confirm approval of the selected ' 'versions without notifying the developer.'), 'comments': False, 'available': (self.addon.type != amo.ADDON_STATICTHEME and version_is_unlisted and is_appropriate_reviewer), } 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 and is_reviewer) } 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 and is_reviewer) } 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': (is_reviewer) } return OrderedDict(((key, action) for key, action in actions.items() if action['available']))
def is_appropriate_reviewer(addon, channel): return (acl.is_reviewer(request, addon) if channel == amo.RELEASE_CHANNEL_LISTED else acl.check_unlisted_addons_reviewer(request))
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 # 2 kind of checks are made for the review page. # - Base permission checks to access the review page itself, done in # the review() view # - A more specific check for each action, done below, restricting # their availability while not affecting whether the user can see # the review page or not. version_is_unlisted = (self.version and self.version.channel == amo.RELEASE_CHANNEL_UNLISTED) promoted_group = self.addon.promoted_group(currently_approved=False) # Default permissions / admin needed values if it's just a regular # code review, nothing fancy. permission = amo.permissions.ADDONS_REVIEW permission_post_review = amo.permissions.ADDONS_REVIEW is_admin_needed = (self.addon.needs_admin_content_review or self.addon.needs_admin_code_review) is_admin_needed_post_review = is_admin_needed # More complex/specific cases. if promoted_group == RECOMMENDED: permission = amo.permissions.ADDONS_RECOMMENDED_REVIEW permission_post_review = permission elif version_is_unlisted: is_admin_needed = self.addon.needs_admin_code_review permission = amo.permissions.ADDONS_REVIEW_UNLISTED permission_post_review = permission elif promoted_group.admin_review: is_admin_needed = is_admin_needed_post_review = True elif self.content_review: is_admin_needed = self.addon.needs_admin_content_review permission = amo.permissions.ADDONS_CONTENT_REVIEW elif self.addon.type == amo.ADDON_STATICTHEME: is_admin_needed = self.addon.needs_admin_theme_review permission = amo.permissions.STATIC_THEMES_REVIEW permission_post_review = permission # In addition, if the latest (or current for post-review) version is # pending rejection, an admin is needed. if self.version and self.version.pending_rejection: is_admin_needed = True if (self.addon.current_version and self.addon.current_version.pending_rejection): is_admin_needed_post_review = True # Whatever permission values we set, we override if an admin is needed. if is_admin_needed: permission = amo.permissions.REVIEWS_ADMIN if is_admin_needed_post_review: permission_post_review = amo.permissions.REVIEWS_ADMIN # Is the current user a reviewer for this kind of add-on ? is_reviewer = acl.is_reviewer(request, self.addon) # Is the current user an appropriate reviewer, not only for this kind # of add-on, but also for the state the add-on is in ? (Allows more # impactful actions). is_appropriate_reviewer = acl.action_allowed_user( request.user, permission) is_appropriate_reviewer_post_review = acl.action_allowed_user( request.user, permission_post_review) addon_is_complete = self.addon.status not in (amo.STATUS_NULL, amo.STATUS_DELETED) addon_is_incomplete_and_version_is_unlisted = (self.addon.status == amo.STATUS_NULL and version_is_unlisted) addon_is_reviewable = (addon_is_complete or addon_is_incomplete_and_version_is_unlisted) version_is_unreviewed = self.version and self.version.is_unreviewed addon_is_valid = self.addon.is_public() or self.addon.is_unreviewed() addon_is_valid_and_version_is_listed = (addon_is_valid and self.version and self.version.channel == amo.RELEASE_CHANNEL_LISTED) current_version_is_listed_and_auto_approved = ( self.version and self.version.channel == amo.RELEASE_CHANNEL_LISTED and self.addon.current_version and self.addon.current_version.was_auto_approved) version_is_blocked = self.version and self.version.is_blocked promoted_subscription_okay = ( not promoted_group or not self.addon.promotedaddon.has_pending_subscription) # Special logic for availability of reject multiple action: if version_is_unlisted: can_reject_multiple = is_appropriate_reviewer elif (self.content_review or promoted_group.pre_review or self.addon.type == amo.ADDON_STATICTHEME): can_reject_multiple = (addon_is_valid_and_version_is_listed and is_appropriate_reviewer) else: # When doing a code review, this action is also available to # users with Addons:PostReview even if the current version hasn't # been auto-approved, provided that the add-on isn't marked as # needing admin review. can_reject_multiple = addon_is_valid_and_version_is_listed and ( is_appropriate_reviewer or is_appropriate_reviewer_post_review) # Definitions for all actions. actions['public'] = { 'method': self.handler.approve_latest_version, 'minimal': False, 'details': _('This will approve, sign, and publish this ' 'version. The comments will be sent to the ' 'developer.'), 'label': _('Approve'), 'available': (not self.content_review and addon_is_reviewable and version_is_unreviewed and is_appropriate_reviewer and not version_is_blocked and promoted_subscription_okay) } actions['reject'] = { 'method': self.handler.reject_latest_version, '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': ( not self.content_review and # We specifically don't let the individual reject action be # available for unlisted review. `reject_latest_version` isn't # currently implemented for unlisted. addon_is_valid_and_version_is_listed and version_is_unreviewed and is_appropriate_reviewer) } actions['approve_content'] = { 'method': self.handler.approve_content, 'label': _('Approve Content'), 'details': _('This records your approbation of the ' 'content of the latest public version, ' 'without notifying the developer.'), 'minimal': False, 'comments': False, 'available': (self.content_review and addon_is_valid_and_version_is_listed and is_appropriate_reviewer), } 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': (not self.content_review and addon_is_valid_and_version_is_listed and current_version_is_listed_and_auto_approved and is_appropriate_reviewer_post_review), } actions['reject_multiple_versions'] = { 'method': self.handler.reject_multiple_versions, 'label': _('Reject Multiple Versions'), 'minimal': True, 'delayable': not version_is_unlisted, 'versions': True, 'details': _('This will reject the selected versions. ' 'The comments will be sent to the developer.'), 'available': (can_reject_multiple), } actions['block_multiple_versions'] = { 'method': self.handler.block_multiple_versions, 'label': _('Block Multiple Versions'), 'minimal': True, 'versions': True, 'comments': False, 'details': _('This will disable the selected approved ' 'versions silently, and open up the block creation ' 'admin page.'), 'available': (self.addon.type != amo.ADDON_STATICTHEME and version_is_unlisted and is_appropriate_reviewer), } actions['confirm_multiple_versions'] = { 'method': self.handler.confirm_multiple_versions, 'label': _('Confirm Multiple Versions'), 'minimal': True, 'versions': True, 'details': _('This will confirm approval of the selected ' 'versions without notifying the developer.'), 'comments': False, 'available': (self.addon.type != amo.ADDON_STATICTHEME and version_is_unlisted and is_appropriate_reviewer), } 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 and is_reviewer and (not promoted_group.admin_review or is_appropriate_reviewer)) } 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 and is_reviewer) } 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': (is_reviewer) } return OrderedDict(((key, action) for key, action in actions.items() if action['available']))