def test_is_submission_allowed_bypassing_read_dev_agreement_restricted( self, incr_mock): # Mix of test_is_submission_allowed_email_restricted() and # test_is_submission_allowed_bypassing_read_dev_agreement() above: # this time, we're restricted by email while bypassing the read dev # agreement check. This ensures even when bypassing that check, we # still record everything properly when restricting. EmailUserRestriction.objects.create( email_pattern=self.request.user.email) checker = RestrictionChecker(request=self.request) assert not checker.is_submission_allowed(check_dev_agreement=False) assert checker.get_error_message() == ( 'The email address used for your account is not ' 'allowed for add-on submission.') assert incr_mock.call_count == 2 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_submission_allowed.EmailUserRestriction.failure', ) assert incr_mock.call_args_list[1][0] == ( 'RestrictionChecker.is_submission_allowed.failure', ) assert UserRestrictionHistory.objects.count() == 1 history = UserRestrictionHistory.objects.get() assert history.get_restriction_display() == 'EmailUserRestriction' assert history.user == self.request.user assert history.last_login_ip == self.request.user.last_login_ip assert history.ip_address == '10.0.0.1'
def has_permission(self, request, view): checker = RestrictionChecker(request=request) if not checker.is_submission_allowed(): self.message = checker.get_error_message() self.code = 'permission_denied_restriction' return False return True
def test_is_auto_approval_allowed_email_restricted(self, incr_mock): EmailUserRestriction.objects.create( email_pattern=self.request.user.email, restriction_type=RESTRICTION_TYPES.APPROVAL, ) upload = FileUpload.objects.create( user=self.request.user, ip_address='10.0.0.2', source=amo.UPLOAD_SOURCE_DEVHUB, ) incr_mock.reset_mock() checker = RestrictionChecker(upload=upload) assert not checker.is_auto_approval_allowed() assert incr_mock.call_count == 2 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_auto_approval_allowed.EmailUserRestriction.failure', ) assert incr_mock.call_args_list[1][0] == ( 'RestrictionChecker.is_auto_approval_allowed.failure', ) assert UserRestrictionHistory.objects.count() == 1 history = UserRestrictionHistory.objects.get() assert history.get_restriction_display() == 'EmailUserRestriction' assert history.user == self.request.user assert history.last_login_ip == self.request.user.last_login_ip assert history.ip_address == '10.0.0.2'
def test_is_submission_allowed_pass(self, incr_mock): checker = RestrictionChecker(request=self.request) assert checker.is_submission_allowed() assert incr_mock.call_count == 1 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_submission_allowed.success', ) assert not UserRestrictionHistory.objects.exists()
def test_user_is_allowed_to_bypass_restrictions(self, incr_mock): IPNetworkUserRestriction.objects.create(network='10.0.0.0/24') EmailUserRestriction.objects.create(email_pattern=self.request.user.email) self.request.user.update(bypass_upload_restrictions=True) checker = RestrictionChecker(request=self.request) assert checker.is_submission_allowed() assert not UserRestrictionHistory.objects.exists() assert incr_mock.call_count == 0
def test_is_submission_allowed_bypassing_read_dev_agreement( self, incr_mock): self.request.user.update(read_dev_agreement=None) checker = RestrictionChecker(request=self.request) assert checker.is_submission_allowed(check_dev_agreement=False) assert incr_mock.call_count == 1 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_submission_allowed.success', ) assert not UserRestrictionHistory.objects.exists()
def test_is_auto_approval_allowed_email_restricted_only_for_submission( self, incr_mock): # Test with a submission restriction (the default): approval should be allowed. EmailUserRestriction.objects.create( email_pattern=self.request.user.email) upload = FileUpload.objects.create(user=self.request.user, ip_address='10.0.0.2') incr_mock.reset_mock() checker = RestrictionChecker(upload=upload) assert checker.is_auto_approval_allowed() assert incr_mock.call_count == 1 assert UserRestrictionHistory.objects.count() == 0 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_auto_approval_allowed.success', )
def test_is_submission_allowed_with_mocks(self, incr_mock): checker = RestrictionChecker(request=self.request) with ExitStack() as stack: allow_submission_mocks = [ stack.enter_context(mock.patch.object(choice[1], 'allow_submission')) for choice in checker.restriction_choices ] allow_auto_approval_mocks = [ stack.enter_context(mock.patch.object(choice[1], 'allow_auto_approval')) for choice in checker.restriction_choices ] assert checker.is_submission_allowed() for restriction_mock in allow_submission_mocks: assert restriction_mock.call_count == 1 for restriction_mock in allow_auto_approval_mocks: assert restriction_mock.call_count == 0
def test_is_submission_allowed_ip_restricted(self, incr_mock): IPNetworkUserRestriction.objects.create(network='10.0.0.0/24') checker = RestrictionChecker(request=self.request) assert not checker.is_submission_allowed() assert checker.get_error_message() == ( 'Multiple add-ons violating our policies have been submitted ' 'from your location. The IP address has been blocked.') assert incr_mock.call_count == 2 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_submission_allowed.IPNetworkUserRestriction.failure', ) assert incr_mock.call_args_list[1][0] == ( 'RestrictionChecker.is_submission_allowed.failure', ) assert UserRestrictionHistory.objects.count() == 1 history = UserRestrictionHistory.objects.get() assert history.get_restriction_display() == 'IPNetworkUserRestriction' assert history.user == self.request.user assert history.last_login_ip == self.request.user.last_login_ip assert history.ip_address == '10.0.0.1'
def test_is_submission_allowed_email_restricted(self, incr_mock): EmailUserRestriction.objects.create( email_pattern=self.request.user.email) checker = RestrictionChecker(request=self.request) assert not checker.is_submission_allowed() assert checker.get_error_message() == ( 'The email address used for your account is not ' 'allowed for add-on submission.') assert incr_mock.call_count == 2 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_submission_allowed.EmailUserRestriction.failure', ) assert incr_mock.call_args_list[1][0] == ( 'RestrictionChecker.is_submission_allowed.failure', ) assert UserRestrictionHistory.objects.count() == 1 history = UserRestrictionHistory.objects.get() assert history.get_restriction_display() == 'EmailUserRestriction' assert history.user == self.request.user assert history.last_login_ip == self.request.user.last_login_ip assert history.ip_address == '10.0.0.1'
def test_is_auto_approval_allowed_with_mocks(self, incr_mock): upload = FileUpload.objects.create(user=self.request.user, ip_address='10.0.0.2') checker = RestrictionChecker(upload=upload) with ExitStack() as stack: allow_submission_mocks = [ stack.enter_context( mock.patch.object(choice[1], 'allow_submission')) for choice in checker.restriction_choices ] allow_auto_approval_mocks = [ stack.enter_context( mock.patch.object(choice[1], 'allow_auto_approval')) for choice in checker.restriction_choices ] assert checker.is_auto_approval_allowed() for restriction_mock in allow_submission_mocks: assert restriction_mock.call_count == 0 for restriction_mock in allow_auto_approval_mocks: assert restriction_mock.call_count == 1
def test_is_submission_allowed_hasnt_read_agreement(self, incr_mock): self.request.user.update(read_dev_agreement=None) checker = RestrictionChecker(request=self.request) assert not checker.is_submission_allowed() assert checker.get_error_message() == ( 'Before starting, please read and accept our Firefox Add-on ' 'Distribution Agreement as well as our Review Policies and Rules. ' 'The Firefox Add-on Distribution Agreement also links to our ' 'Privacy Notice which explains how we handle your information.') assert incr_mock.call_count == 2 assert incr_mock.call_args_list[0][0] == ( 'RestrictionChecker.is_submission_allowed.DeveloperAgreementRestriction.' 'failure', ) assert incr_mock.call_args_list[1][0] == ( 'RestrictionChecker.is_submission_allowed.failure', ) assert UserRestrictionHistory.objects.count() == 1 history = UserRestrictionHistory.objects.get() assert history.get_restriction_display() == ( 'DeveloperAgreementRestriction') assert history.user == self.request.user assert history.last_login_ip == self.request.user.last_login_ip assert history.ip_address == '10.0.0.1'
def from_upload(cls, upload, addon, selected_apps, channel, parsed_data=None): """ Create a Version instance and corresponding File(s) from a FileUpload, an Addon, a list of compatible app ids, a channel id and the parsed_data generated by parse_addon(). Note that it's the caller's responsability to ensure the file is valid. We can't check for that here because an admin may have overridden the validation results. """ from olympia.addons.models import AddonReviewerFlags from olympia.addons.utils import RestrictionChecker from olympia.git.utils import create_git_extraction_entry assert parsed_data is not None if addon.status == amo.STATUS_DISABLED: raise VersionCreateError( 'Addon is Mozilla Disabled; no new versions are allowed.') if upload.addon and upload.addon != addon: raise VersionCreateError( 'FileUpload was made for a different Addon') if not upload.user or not upload.ip_address or not upload.source: raise VersionCreateError( 'FileUpload does not have some required fields') if not upload.user.last_login_ip or not upload.user.email: raise VersionCreateError( 'FileUpload user does not have some required fields') license_id = None if channel == amo.RELEASE_CHANNEL_LISTED: previous_version = addon.find_latest_version(channel=channel, exclude=()) if previous_version and previous_version.license_id: license_id = previous_version.license_id approval_notes = None if parsed_data.get('is_mozilla_signed_extension'): approval_notes = ( 'This version has been signed with Mozilla internal certificate.' ) version = cls.objects.create( addon=addon, approval_notes=approval_notes, version=parsed_data['version'], license_id=license_id, channel=channel, ) email = upload.user.email if upload.user and upload.user.email else '' with core.override_remote_addr(upload.ip_address): # The following log statement is used by foxsec-pipeline. # We override the IP because it might be called from a task and we # want the original IP from the submitter. log.info( f'New version: {version!r} ({version.id}) from {upload!r}', extra={ 'email': email, 'guid': addon.guid, 'upload': upload.uuid.hex, 'user_id': upload.user_id, 'from_api': upload.source == amo.UPLOAD_SOURCE_API, }, ) activity.log_create(amo.LOG.ADD_VERSION, version, addon, user=upload.user or get_task_user()) if addon.type == amo.ADDON_STATICTHEME: # We don't let developers select apps for static themes selected_apps = [app.id for app in amo.APP_USAGE] compatible_apps = {} for app in parsed_data.get('apps', []): if app.id not in selected_apps: # If the user chose to explicitly deselect Firefox for Android # we're not creating the respective `ApplicationsVersions` # which will have this add-on then be listed only for # Firefox specifically. continue compatible_apps[app.appdata] = ApplicationsVersions( version=version, min=app.min, max=app.max, application=app.id) compatible_apps[app.appdata].save() # Pre-generate _compatible_apps property to avoid accidentally # triggering queries with that instance later. version._compatible_apps = compatible_apps # Create relevant file and update the all_files cached property on the # Version, because we might need it afterwards. version.all_files = [ File.from_upload( upload=upload, version=version, parsed_data=parsed_data, ) ] version.inherit_nomination(from_statuses=[amo.STATUS_AWAITING_REVIEW]) version.disable_old_files() # After the upload has been copied to its permanent location, delete it # from storage. Keep the FileUpload instance (it gets cleaned up by a # cron eventually some time after its creation, in amo.cron.gc()), # making sure it's associated with the add-on instance. storage.delete(upload.path) upload.path = '' if upload.addon is None: upload.addon = addon upload.save() version_uploaded.send(instance=version, sender=Version) if version.is_webextension: if (waffle.switch_is_active('enable-yara') or waffle.switch_is_active('enable-customs') or waffle.switch_is_active('enable-wat')): ScannerResult.objects.filter(upload_id=upload.id).update( version=version) if waffle.switch_is_active('enable-uploads-commit-to-git-storage'): # Schedule this version for git extraction. transaction.on_commit( lambda: create_git_extraction_entry(version=version)) # Generate a preview and icon for listed static themes if (addon.type == amo.ADDON_STATICTHEME and channel == amo.RELEASE_CHANNEL_LISTED): theme_data = parsed_data.get('theme', {}) generate_static_theme_preview(theme_data, version.pk) # Reset add-on reviewer flags to disable auto-approval and require # admin code review if the package has already been signed by mozilla. reviewer_flags_defaults = {} is_mozilla_signed = parsed_data.get('is_mozilla_signed_extension') if upload.validation_timeout: reviewer_flags_defaults['needs_admin_code_review'] = True if is_mozilla_signed and addon.type != amo.ADDON_LPAPP: reviewer_flags_defaults['needs_admin_code_review'] = True reviewer_flags_defaults['auto_approval_disabled'] = True # Check if the approval should be restricted if not RestrictionChecker(upload=upload).is_auto_approval_allowed(): flag = ('auto_approval_disabled' if channel == amo.RELEASE_CHANNEL_LISTED else 'auto_approval_disabled_unlisted') reviewer_flags_defaults[flag] = True if reviewer_flags_defaults: AddonReviewerFlags.objects.update_or_create( addon=addon, defaults=reviewer_flags_defaults) # Authors need to be notified about auto-approval delay again since # they are submitting a new version. addon.reset_notified_about_auto_approval_delay() # Track the time it took from first upload through validation # (and whatever else) until a version was created. upload_start = utc_millesecs_from_epoch(upload.created) now = datetime.datetime.now() now_ts = utc_millesecs_from_epoch(now) upload_time = now_ts - upload_start log.info('Time for version {version} creation from upload: {delta}; ' 'created={created}; now={now}'.format(delta=upload_time, version=version, created=upload.created, now=now)) statsd.timing('devhub.version_created_from_upload', upload_time) return version
def test_is_auto_approval_allowed_no_upload_raises_improperly_configured( self, incr_mock): checker = RestrictionChecker(request=self.request) with self.assertRaises(ImproperlyConfigured): assert checker.is_auto_approval_allowed()
def test_is_submission_allowed_no_request_raises_improperly_configured( self, incr_mock): checker = RestrictionChecker(upload=mock.Mock()) with self.assertRaises(ImproperlyConfigured): assert checker.is_submission_allowed()
def test_no_request_or_upload_at_init(self, incr_mock): with self.assertRaises(ImproperlyConfigured): RestrictionChecker()