def test_group_management(self): x = UserProfile.objects.get(pk=10968) assert not action_allowed_user(x, 'Admin', '%') do_adduser('10968', '1') assert action_allowed_user(x, 'Admin', '%') do_removeuser('10968', '1') assert not action_allowed_user(x, 'Admin', '%')
def test_group_management(self): user = UserProfile.objects.get(pk=10968) assert not action_allowed_user(user, amo.permissions.ADMIN_TOOLS) management.call_command('addusertogroup', '10968', '1') del user.groups_list assert action_allowed_user(user, amo.permissions.ADMIN_TOOLS) management.call_command('removeuserfromgroup', '10968', '1') del user.groups_list assert not action_allowed_user(user, amo.permissions.ADMIN_TOOLS)
def inner(view, request, guid=None, **kwargs): try: addon = Addon.unfiltered.get(guid=guid) except Addon.DoesNotExist: if allow_missing: addon = None else: return Response({'error': _('Could not find add-on with ' 'id "{}".').format(guid)}, status=status.HTTP_404_NOT_FOUND) # Call the view if there is no add-on, the current user is an # auther of the add-on or the current user is an admin and the # request is a GET. has_perm = ( addon is None or (addon.has_author(request.user) or (request.method == 'GET' and acl.action_allowed_user( request.user, 'Addons', 'Edit')))) if has_perm: return fn(view, request, addon=addon, **kwargs) else: return Response( {'error': _('You do not own this addon.')}, status=status.HTTP_403_FORBIDDEN)
def inner(view, request, **kwargs): guid = kwargs.get('guid', None) try: if guid is None: raise Addon.DoesNotExist('No GUID') addon = Addon.unfiltered.get(guid=guid) except Addon.DoesNotExist: if allow_missing: addon = None else: msg = ugettext( 'Could not find add-on with id "{}".').format(guid) return Response( {'error': msg}, status=status.HTTP_404_NOT_FOUND) # Call the view if there is no add-on, the current user is an # auther of the add-on or the current user is an admin and the # request is a GET. has_perm = ( addon is None or (addon.has_author(request.user) or (request.method == 'GET' and acl.action_allowed_user( request.user, amo.permissions.ADDONS_EDIT)))) if has_perm: return fn(view, request, addon=addon, **kwargs) else: return Response( {'error': ugettext('You do not own this addon.')}, status=status.HTTP_403_FORBIDDEN)
def action_from_user(user, version): review_perm = ('Review' if version.channel == amo.RELEASE_CHANNEL_LISTED else 'ReviewUnlisted') if version.addon.authors.filter(pk=user.pk).exists(): return amo.LOG.DEVELOPER_REPLY_VERSION elif acl.action_allowed_user(user, 'Addons', review_perm): return amo.LOG.REVIEWER_REPLY_VERSION
def is_staff(self): """Property indicating whether the user should be able to log in to the django admin tools. Does not guarantee that the user will then be able to do anything, as each module can have its own permission checks. (see has_module_perms() and has_perm())""" from olympia.access import acl return acl.action_allowed_user(self, amo.permissions.ANY_ADMIN)
def log_and_notify(action, comments, note_creator, version): log_kwargs = { 'user': note_creator, 'created': datetime.datetime.now(), } if comments: log_kwargs['details'] = { 'comments': comments, 'version': version.version} else: # Just use the name of the action if no comments provided. Alas we # can't know the locale of recipient, and our templates are English # only so prevent language jumble by forcing into en-US. with no_translation(): comments = '%s' % action.short note = amo.log(action, version.addon, version, **log_kwargs) # Collect reviewers involved with this version. review_perm = ('Review' if version.channel == amo.RELEASE_CHANNEL_LISTED else 'ReviewUnlisted') log_users = { alog.user for alog in ActivityLog.objects.for_version(version) if acl.action_allowed_user(alog.user, 'Addons', review_perm)} # Collect add-on authors (excl. the person who sent the email.) addon_authors = set(version.addon.authors.all()) - {note_creator} # Collect staff that want a copy of the email staff_cc = set( UserProfile.objects.filter(groups__name=ACTIVITY_MAIL_GROUP)) # If task_user doesn't exist that's no big issue (i.e. in tests) try: task_user = {get_task_user()} except UserProfile.DoesNotExist: task_user = set() # Collect reviewers on the thread (excl. the email sender and task user for # automated messages). reviewers = ((log_users | staff_cc) - addon_authors - task_user - {note_creator}) author_context_dict = { 'name': version.addon.name, 'number': version.version, 'author': note_creator.name, 'comments': comments, 'url': absolutify(version.addon.get_dev_url('versions')), 'SITE_URL': settings.SITE_URL, } reviewer_context_dict = author_context_dict.copy() reviewer_context_dict['url'] = absolutify( reverse('editors.review', args=[version.addon.pk], add_prefix=False)) # Not being localised because we don't know the recipients locale. subject = 'Mozilla Add-ons: %s Updated' % version.addon.name template = loader.get_template('activity/emails/developer.txt') send_activity_mail( subject, template.render(Context(author_context_dict)), version, addon_authors, settings.EDITORS_EMAIL) send_activity_mail( subject, template.render(Context(reviewer_context_dict)), version, reviewers, settings.EDITORS_EMAIL) return note
def test_can_create_an_admin_user(self): group = Group.objects.create(rules='*:*', name='admin group') res = self.post(self.url, {'group': 'admin'}) assert res.status_code == 201, res.content user = UserProfile.objects.get(pk=res.data['user_id']) assert action_allowed_user(user, 'Any', 'DamnThingTheyWant') assert res.data['groups'] == [(group.pk, group.name, group.rules)]
def has_perm(self, perm, obj=None): """Determine what the user can do in the django admin tools. perm is in the form "<app>.<action>_<model>". """ from olympia.access import acl return acl.action_allowed_user( self, amo.permissions.DJANGO_PERMISSIONS_MAPPING[perm])
def action_from_user(user, version): review_perm = (amo.permissions.ADDONS_REVIEW if version.channel == amo.RELEASE_CHANNEL_LISTED else amo.permissions.ADDONS_REVIEW_UNLISTED) if version.addon.authors.filter(pk=user.pk).exists(): return amo.LOG.DEVELOPER_REPLY_VERSION elif acl.action_allowed_user(user, review_perm): return amo.LOG.REVIEWER_REPLY_VERSION
def template_from_user(user, version): review_perm = (amo.permissions.ADDONS_REVIEW if version.channel == amo.RELEASE_CHANNEL_LISTED else amo.permissions.ADDONS_REVIEW_UNLISTED) template = 'activity/emails/developer.txt' if (not version.addon.authors.filter(pk=user.pk).exists() and acl.action_allowed_user(user, review_perm)): template = 'activity/emails/from_reviewer.txt' return loader.get_template(template)
def __init__(self, *args, **kw): self.helper = kw.pop('helper') super(ReviewForm, self).__init__(*args, **kw) # Info request deadline needs to be readonly unless we're an admin. user = self.helper.handler.user deadline_widget_attributes = {} info_request_deadline = self.fields['info_request_deadline'] if not acl.action_allowed_user(user, amo.permissions.REVIEWS_ADMIN): info_request_deadline.min_value = info_request_deadline.initial info_request_deadline.max_value = info_request_deadline.initial deadline_widget_attributes['readonly'] = 'readonly' deadline_widget_attributes.update({ 'min': info_request_deadline.min_value, 'max': info_request_deadline.max_value, }) info_request_deadline.widget.attrs.update(deadline_widget_attributes) # With the helper, we now have the add-on and can set queryset on the # versions field correctly. Small optimization: we only need to do this # if the reject_multiple_versions action is available, otherwise we # don't really care about this field. if 'reject_multiple_versions' in self.helper.actions: self.fields['versions'].queryset = ( self.helper.addon.versions.distinct().filter( channel=amo.RELEASE_CHANNEL_LISTED, files__status__in=(amo.STATUS_PUBLIC, amo.STATUS_AWAITING_REVIEW)). order_by('created')) # For the canned responses, we're starting with an empty one, which # will be hidden via CSS. canned_choices = [ ['', [('', ugettext('Choose a canned response...'))]]] canned_type = ( amo.CANNED_RESPONSE_THEME if self.helper.addon.type == amo.ADDON_STATICTHEME else amo.CANNED_RESPONSE_ADDON) responses = CannedResponse.objects.filter(type=canned_type) # Loop through the actions (public, etc). for k, action in self.helper.actions.iteritems(): action_choices = [[c.response, c.name] for c in responses if c.sort_group and k in c.sort_group.split(',')] # Add the group of responses to the canned_choices array. if action_choices: canned_choices.append([action['label'], action_choices]) # Now, add everything not in a group. for r in responses: if not r.sort_group: canned_choices.append([r.response, r.name]) self.fields['canned_response'].choices = canned_choices self.fields['action'].choices = [ (k, v['label']) for k, v in self.helper.actions.items()]
def log_and_notify(action, comments, note_creator, version): log_kwargs = { 'user': note_creator, 'created': datetime.datetime.now(), 'details': { 'comments': comments, 'version': version.version } } note = amo.log(action, version.addon, version, **log_kwargs) # Collect reviewers involved with this version. review_perm = ('Review' if version.channel == amo.RELEASE_CHANNEL_LISTED else 'ReviewUnlisted') log_users = { alog.user for alog in ActivityLog.objects.for_version(version) if acl.action_allowed_user(alog.user, 'Addons', review_perm) } # Collect add-on authors (excl. the person who sent the email.) addon_authors = set(version.addon.authors.all()) - {note_creator} # Collect staff that want a copy of the email staff_cc = set( UserProfile.objects.filter(groups__name=ACTIVITY_MAIL_GROUP)) # If task_user doesn't exist that's no big issue (i.e. in tests) try: task_user = {get_task_user()} except UserProfile.DoesNotExist: task_user = set() # Collect reviewers on the thread (excl. the email sender and task user for # automated messages). reviewers = ((log_users | staff_cc) - addon_authors - task_user - {note_creator}) author_context_dict = { 'name': version.addon.name, 'number': version.version, 'author': note_creator.name, 'comments': comments, 'url': version.addon.get_dev_url('versions'), 'SITE_URL': settings.SITE_URL, } reviewer_context_dict = author_context_dict.copy() reviewer_context_dict['url'] = absolutify( reverse('editors.review', args=[version.addon.pk], add_prefix=False)) # Not being localised because we don't know the recipients locale. subject = 'Mozilla Add-ons: %s Updated' % version.addon.name template = loader.get_template('activity/emails/developer.txt') send_activity_mail(subject, template.render(Context(author_context_dict)), version, addon_authors, settings.EDITORS_EMAIL) send_activity_mail(subject, template.render(Context(reviewer_context_dict)), version, reviewers, settings.EDITORS_EMAIL) return note
def needs_tougher_password(user): from olympia.access import acl return (acl.action_allowed_user(user, 'Admin', '%') or acl.action_allowed_user(user, 'Addons', 'Edit') or acl.action_allowed_user(user, 'Addons', 'Review') or acl.action_allowed_user(user, 'Apps', 'Review') or acl.action_allowed_user(user, 'Personas', 'Review') or acl.action_allowed_user(user, 'Users', 'Edit'))
def send_notifications(signal=None, sender=None, **kw): if sender.is_beta or sender.channel != amo.RELEASE_CHANNEL_LISTED: return subscribers = sender.addon.reviewersubscription_set.all() if not subscribers: return for subscriber in subscribers: user = subscriber.user is_reviewer = (user and not user.deleted and user.email and acl.action_allowed_user( user, amo.permissions.ADDONS_REVIEW)) if is_reviewer: subscriber.send_notification(sender)
def needs_tougher_password(user): from olympia.access import acl return ( acl.action_allowed_user(user, "Admin", "%") or acl.action_allowed_user(user, "Addons", "Edit") or acl.action_allowed_user(user, "Addons", "Review") or acl.action_allowed_user(user, "Apps", "Review") or acl.action_allowed_user(user, "Personas", "Review") or acl.action_allowed_user(user, "Users", "Edit") )
def send_notifications(signal=None, sender=None, **kw): if sender.is_beta: return subscribers = sender.addon.reviewersubscription_set.all() if not subscribers: return for subscriber in subscribers: user = subscriber.user is_reviewer = ( user and not user.deleted and user.email and acl.action_allowed_user(user, amo.permissions.ADDONS_REVIEW)) if is_reviewer: subscriber.send_notification(sender) subscriber.delete()
def verify_mozilla_trademark(name, user, form=None): skip_trademark_check = ( user and user.is_authenticated and action_allowed_user(user, amo.permissions.TRADEMARK_BYPASS) ) def _check(name): name = normalize_string(name, strip_punctuation=True).lower() for symbol in amo.MOZILLA_TRADEMARK_SYMBOLS: violates_trademark = name.count(symbol) > 1 or ( name.count(symbol) >= 1 and not name.endswith(' for {}'.format(symbol)) ) if violates_trademark: raise forms.ValidationError( gettext( 'Add-on names cannot contain the Mozilla or ' 'Firefox trademarks.' ) ) if not skip_trademark_check: if not isinstance(name, dict): _check(name) else: for locale, localized_name in name.items(): try: _check(localized_name) except forms.ValidationError as exc: if form is not None: for message in exc.messages: error_message = LocaleErrorMessage( message=message, locale=locale ) form.add_error('name', error_message) else: raise return name
def is_staff(self): from olympia.access import acl return acl.action_allowed_user(self, 'Admin', '%')
def log_and_notify(action, comments, note_creator, version, perm_setting=None, detail_kwargs=None): log_kwargs = { 'user': note_creator, 'created': datetime.datetime.now(), } if detail_kwargs is None: detail_kwargs = {} if comments: detail_kwargs['version'] = version.version detail_kwargs['comments'] = comments else: # Just use the name of the action if no comments provided. Alas we # can't know the locale of recipient, and our templates are English # only so prevent language jumble by forcing into en-US. with no_translation(): comments = '%s' % action.short if detail_kwargs: log_kwargs['details'] = detail_kwargs note = ActivityLog.create(action, version.addon, version, **log_kwargs) if not note: return # Collect reviewers involved with this version. review_perm = (amo.permissions.ADDONS_REVIEW if version.channel == amo.RELEASE_CHANNEL_LISTED else amo.permissions.ADDONS_REVIEW_UNLISTED) log_users = { alog.user for alog in ActivityLog.objects.for_version(version) if acl.action_allowed_user(alog.user, review_perm) } # Collect add-on authors (excl. the person who sent the email.) addon_authors = set(version.addon.authors.all()) - {note_creator} # Collect staff that want a copy of the email staff = set(UserProfile.objects.filter(groups__name=ACTIVITY_MAIL_GROUP)) # If task_user doesn't exist that's no big issue (i.e. in tests) try: task_user = {get_task_user()} except UserProfile.DoesNotExist: task_user = set() # Collect reviewers on the thread (excl. the email sender and task user for # automated messages). reviewers = log_users - addon_authors - task_user - {note_creator} staff_cc = staff - reviewers - addon_authors - task_user - {note_creator} author_context_dict = { 'name': version.addon.name, 'number': version.version, 'author': note_creator.name, 'comments': comments, 'url': absolutify(version.addon.get_dev_url('versions')), 'SITE_URL': settings.SITE_URL, 'email_reason': 'you are an author of this add-on' } reviewer_context_dict = author_context_dict.copy() reviewer_context_dict['url'] = absolutify( reverse('editors.review', kwargs={ 'addon_id': version.addon.pk, 'channel': amo.CHANNEL_CHOICES_API[version.channel] }, add_prefix=False)) reviewer_context_dict['email_reason'] = 'you reviewed this add-on' staff_cc_context_dict = reviewer_context_dict.copy() staff_cc_context_dict['email_reason'] = ( 'you are member of the activity email cc group') # Not being localised because we don't know the recipients locale. with translation.override('en-US'): subject = u'Mozilla Add-ons: %s %s' % (version.addon.name, version.version) template = template_from_user(note_creator, version) from_email = formataddr((note_creator.name, NOTIFICATIONS_FROM_EMAIL)) send_activity_mail(subject, template.render(author_context_dict), version, addon_authors, from_email, note.id, perm_setting) send_activity_mail(subject, template.render(reviewer_context_dict), version, reviewers, from_email, note.id, perm_setting) send_activity_mail(subject, template.render(staff_cc_context_dict), version, staff_cc, from_email, note.id, perm_setting) if action == amo.LOG.DEVELOPER_REPLY_VERSION: version.update(has_info_request=False) return note
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 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']))
def is_adminish(user): return (user and acl.action_allowed_user(user, amo.permissions.USERS_EDIT))
def get_serializer_class(self): if (self.self_view or acl.action_allowed_user( self.request.user, amo.permissions.USERS_EDIT)): return UserProfileSerializer else: return PublicUserProfileSerializer
def admin_viewing(self): return acl.action_allowed_user( self.request.user, amo.permissions.USERS_EDIT)
def log_and_notify(action, comments, note_creator, version, perm_setting=None, detail_kwargs=None): log_kwargs = { 'user': note_creator, 'created': datetime.datetime.now(), } if detail_kwargs is None: detail_kwargs = {} if comments: detail_kwargs['version'] = version.version detail_kwargs['comments'] = comments else: # Just use the name of the action if no comments provided. Alas we # can't know the locale of recipient, and our templates are English # only so prevent language jumble by forcing into en-US. with no_translation(): comments = '%s' % action.short if detail_kwargs: log_kwargs['details'] = detail_kwargs note = ActivityLog.create(action, version.addon, version, **log_kwargs) # Collect reviewers involved with this version. review_perm = (amo.permissions.ADDONS_REVIEW if version.channel == amo.RELEASE_CHANNEL_LISTED else amo.permissions.ADDONS_REVIEW_UNLISTED) log_users = { alog.user for alog in ActivityLog.objects.for_version(version) if acl.action_allowed_user(alog.user, review_perm)} # Collect add-on authors (excl. the person who sent the email.) addon_authors = set(version.addon.authors.all()) - {note_creator} # Collect staff that want a copy of the email staff_cc = set( UserProfile.objects.filter(groups__name=ACTIVITY_MAIL_GROUP)) # If task_user doesn't exist that's no big issue (i.e. in tests) try: task_user = {get_task_user()} except UserProfile.DoesNotExist: task_user = set() # Collect reviewers on the thread (excl. the email sender and task user for # automated messages). reviewers = ((log_users | staff_cc) - addon_authors - task_user - {note_creator}) author_context_dict = { 'name': version.addon.name, 'number': version.version, 'author': note_creator.name, 'comments': comments, 'url': absolutify(version.addon.get_dev_url('versions')), 'SITE_URL': settings.SITE_URL, } reviewer_context_dict = author_context_dict.copy() reviewer_context_dict['url'] = absolutify( reverse('editors.review', args=[version.addon.pk], add_prefix=False)) # Not being localised because we don't know the recipients locale. with translation.override('en-US'): subject = u'Mozilla Add-ons: %s %s %s' % ( version.addon.name, version.version, action.short) template = template_from_user(note_creator, version) send_activity_mail( subject, template.render(Context(author_context_dict)), version, addon_authors, settings.EDITORS_EMAIL, perm_setting) send_activity_mail( subject, template.render(Context(reviewer_context_dict)), version, reviewers, settings.EDITORS_EMAIL, perm_setting) return note
def action_from_user(user, version): review_perm = 'Review' if version.addon.is_listed else 'ReviewUnlisted' if version.addon.authors.filter(pk=user.pk).exists(): return amo.LOG.DEVELOPER_REPLY_VERSION elif acl.action_allowed_user(user, 'Addons', review_perm): return amo.LOG.REVIEWER_REPLY_VERSION
def log_and_notify(action, comments, note_creator, version, perm_setting=None, detail_kwargs=None): log_kwargs = { 'user': note_creator, 'created': datetime.datetime.now(), } if detail_kwargs is None: detail_kwargs = {} if comments: detail_kwargs['version'] = version.version detail_kwargs['comments'] = comments else: # Just use the name of the action if no comments provided. Alas we # can't know the locale of recipient, and our templates are English # only so prevent language jumble by forcing into en-US. with no_translation(): comments = '%s' % action.short if detail_kwargs: log_kwargs['details'] = detail_kwargs note = ActivityLog.create(action, version.addon, version, **log_kwargs) if not note: return # Collect reviewers involved with this version. review_perm = (amo.permissions.ADDONS_REVIEW if version.channel == amo.RELEASE_CHANNEL_LISTED else amo.permissions.ADDONS_REVIEW_UNLISTED) log_users = { alog.user for alog in ActivityLog.objects.for_version(version) if acl.action_allowed_user(alog.user, review_perm)} # Collect add-on authors (excl. the person who sent the email.) addon_authors = set(version.addon.authors.all()) - {note_creator} # Collect staff that want a copy of the email staff = set( UserProfile.objects.filter(groups__name=ACTIVITY_MAIL_GROUP)) # If task_user doesn't exist that's no big issue (i.e. in tests) try: task_user = {get_task_user()} except UserProfile.DoesNotExist: task_user = set() # Collect reviewers on the thread (excl. the email sender and task user for # automated messages). reviewers = log_users - addon_authors - task_user - {note_creator} staff_cc = staff - reviewers - addon_authors - task_user - {note_creator} author_context_dict = { 'name': version.addon.name, 'number': version.version, 'author': note_creator.name, 'comments': comments, 'url': absolutify(version.addon.get_dev_url('versions')), 'SITE_URL': settings.SITE_URL, 'email_reason': 'you are an author of this add-on' } reviewer_context_dict = author_context_dict.copy() reviewer_context_dict['url'] = absolutify( reverse('reviewers.review', kwargs={'addon_id': version.addon.pk, 'channel': amo.CHANNEL_CHOICES_API[version.channel]}, add_prefix=False)) reviewer_context_dict['email_reason'] = 'you reviewed this add-on' staff_cc_context_dict = reviewer_context_dict.copy() staff_cc_context_dict['email_reason'] = ( 'you are member of the activity email cc group') # Not being localised because we don't know the recipients locale. with translation.override('en-US'): subject = u'Mozilla Add-ons: %s %s' % ( version.addon.name, version.version) template = template_from_user(note_creator, version) from_email = formataddr((note_creator.name, NOTIFICATIONS_FROM_EMAIL)) send_activity_mail( subject, template.render(author_context_dict), version, addon_authors, from_email, note.id, perm_setting) send_activity_mail( subject, template.render(reviewer_context_dict), version, reviewers, from_email, note.id, perm_setting) send_activity_mail( subject, template.render(staff_cc_context_dict), version, staff_cc, from_email, note.id, perm_setting) if action == amo.LOG.DEVELOPER_REPLY_VERSION: version.update(has_info_request=False) return note
def is_adminish(user): return (user and acl.action_allowed_user( user, amo.permissions.USERS_EDIT))
def test_can_create_a_reviewer_user(self): Group.objects.create(rules='Addons:Review', name='reviewer group') res = self.post(self.url, {'group': 'reviewer'}) assert res.status_code == 201, res.content user = UserProfile.objects.get(pk=res.data['user_id']) assert action_allowed_user(user, 'Addons', 'Review')
def __init__(self, *args, **kw): self.helper = kw.pop('helper') super(ReviewForm, self).__init__(*args, **kw) # Delayed rejection period needs to be readonly unless we're an admin. user = self.helper.handler.user rejection_period_widget_attributes = {} rejection_period = self.fields['delayed_rejection_days'] if not acl.action_allowed_user(user, amo.permissions.REVIEWS_ADMIN): rejection_period.min_value = rejection_period.initial rejection_period.max_value = rejection_period.initial rejection_period_widget_attributes['readonly'] = 'readonly' rejection_period_widget_attributes['min'] = rejection_period.min_value rejection_period_widget_attributes['max'] = rejection_period.max_value rejection_period.widget.attrs.update( rejection_period_widget_attributes) # With the helper, we now have the add-on and can set queryset on the # versions field correctly. Small optimization: we only need to do this # if the relevant actions are available, otherwise we don't really care # about this field. versions_actions = [ k for k in self.helper.actions if self.helper.actions[k].get('versions') ] if versions_actions: if self.helper.version: channel = self.helper.version.channel else: channel = amo.RELEASE_CHANNEL_LISTED statuses = (amo.STATUS_APPROVED, amo.STATUS_AWAITING_REVIEW) self.fields['versions'].widget.versions_actions = versions_actions self.fields['versions'].queryset = (self.helper.addon.versions( manager='unfiltered_for_relations').filter( channel=channel, files__status__in=statuses).no_transforms( ).prefetch_related('files').distinct().order_by('created')) # Reset data-value depending on widget depending on actions # available ([''] added to get an extra '|' at the end). self.fields['versions'].widget.attrs['data-value'] = '|'.join( versions_actions + ['']) # For the canned responses, we're starting with an empty one, which # will be hidden via CSS. canned_choices = [['', [('', gettext('Choose a canned response...'))]]] canned_type = (amo.CANNED_RESPONSE_TYPE_THEME if self.helper.addon.type == amo.ADDON_STATICTHEME else amo.CANNED_RESPONSE_TYPE_ADDON) responses = CannedResponse.objects.filter(type=canned_type) # Loop through the actions (public, etc). for k, action in self.helper.actions.items(): action_choices = [[c.response, c.name] for c in responses if c.sort_group and k in c.sort_group.split(',')] # Add the group of responses to the canned_choices array. if action_choices: canned_choices.append([action['label'], action_choices]) # Now, add everything not in a group. for canned_response in responses: if not canned_response.sort_group: canned_choices.append( [canned_response.response, canned_response.name]) self.fields['canned_response'].choices = canned_choices # Set choices on the action field dynamically to raise an error when # someone tries to use an action they don't have access to. self.fields['action'].choices = [ (k, v['label']) for k, v in self.helper.actions.items() ]
def admin_viewing(self): return acl.action_allowed_user(self.request.user, amo.permissions.USERS_EDIT)
def is_staff(self): from olympia.access import acl return acl.action_allowed_user(self, amo.permissions.ADMIN)
def has_permission(self, request, view): if not request.user.is_authenticated(): return False return acl.action_allowed_user(request.user, self.app, self.action)