Beispiel #1
0
    def test_sign_addon_with_unicode_guid(self):
        self.addon.update(guid='NavratnePeniaze@NávratnéPeniaze')

        signing.sign_file(self.file_)

        signature_info, manifest = _get_signature_details(
            self.file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']

        assert subject_info['common_name'] == 'NavratnePeniaze@NávratnéPeniaze'
        assert manifest.count('Name: ') == 4
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: index.js\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: nsBG7x6peXmndngU43AGIi6CKBM=\n'
            'SHA256-Digest: Hh3yviccEoUvKvoYupqPO+k900wpIMgPFsRMmRW+fGg=\n\n'
            'Name: manifest.json\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: +1L0sNk03EPxDOB6QX3QbtFy8XA=\n'
            'SHA256-Digest: a+UZOkXfCnXKTRM459ip/0OdJt9SxM/DAOkhKTyCsSA=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: xy12EQlU8eCap0SY5C0WMHoNtj8=\n'
            'SHA256-Digest: YdsmjrtOMGyISHs7UgxAXzLHSKoQRGe+NGzc4pDCos8=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n')
    def test_call_signing_too_long_guid_bug_1203365(self):
        long_guid = 'x' * 65
        hashed = hashlib.sha256(force_bytes(long_guid)).hexdigest()
        self.addon.update(guid=long_guid)
        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == hashed
        assert manifest.count('Name: ') == 3
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: yguu1oY209BnHZkqftJFZb8UANQ=\n'
            'SHA256-Digest: BJOnqdLGdmNsM6ZE2FRFOrEUFQd2AYRlg9U/+ETXUgM=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n')
Beispiel #3
0
    def test_call_signing_too_long_guid_bug_1203365(self):
        long_guid = 'x' * 65
        hashed = hashlib.sha256(force_bytes(long_guid)).hexdigest()
        self.addon.update(guid=long_guid)
        signing.sign_file(self.file_)

        signature_info, manifest = _get_signature_details(
            self.file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == hashed
        assert manifest.count('Name: ') == 3
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: V/tyfTP/mpl35QSMyq1MNfwGp/k=\n'
            'SHA256-Digest: kxvbLVQq1TerEUTNXPZjCqfU6n1BG1r2CU+TcAh4bDU=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: BofxIny3xTBMslpCxjYTq2CQMeQ=\n'
            'SHA256-Digest: mxxnbguFIZ9tT5UdvenKu5mOGV5eX4Dll1YSXp/AxvA=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n'
        )
Beispiel #4
0
    def test_sign_addon_with_unicode_guid(self):
        self.addon.update(guid=u'NavratnePeniaze@NávratnéPeniaze')

        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']

        assert (
            subject_info['common_name'] ==
            u'NavratnePeniaze@NávratnéPeniaze')
        assert manifest.count('Name: ') == 3
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: yguu1oY209BnHZkqftJFZb8UANQ=\n'
            'SHA256-Digest: BJOnqdLGdmNsM6ZE2FRFOrEUFQd2AYRlg9U/+ETXUgM=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n'
        )
Beispiel #5
0
def new_theme_version_with_69_properties(old_version):
    timer = StopWatch(
        'addons.tasks.repack_themes_for_69.new_theme_version.')
    timer.start()

    author = get_user()
    # Wrap zip in FileUpload for Version from_upload to consume.
    upload = FileUpload.objects.create(user=author, valid=True)
    filename = uuid.uuid4().hex + '.xpi'
    destination = os.path.join(user_media_path('addons'), 'temp', filename)
    old_xpi = get_filepath(old_version.all_files[0])
    build_69_compatible_theme(
        old_xpi, destination, get_next_version_number(old_version.addon))
    upload.update(path=destination, name=filename)
    timer.log_interval('1.build_xpi')

    # Create addon + version
    parsed_data = parse_addon(upload, addon=old_version.addon, user=author)
    timer.log_interval('2.parse_addon')

    version = Version.from_upload(
        upload, old_version.addon, selected_apps=[amo.FIREFOX.id],
        channel=amo.RELEASE_CHANNEL_LISTED,
        parsed_data=parsed_data)
    timer.log_interval('3.initialize_version')

    # And finally sign the files (actually just one)
    for file_ in version.all_files:
        sign_file(file_)
        file_.update(
            reviewed=datetime.now(),
            status=amo.STATUS_APPROVED)
    timer.log_interval('4.sign_files')

    return version
Beispiel #6
0
 def sign_files(self):
     assert not (self.version and self.version.is_blocked)
     for file_ in self.files:
         if file_.is_experiment:
             ActivityLog.create(
                 amo.LOG.EXPERIMENT_SIGNED, file_, user=self.user)
         sign_file(file_)
Beispiel #7
0
 def test_sign_file_non_ascii_filename(self):
     src = self.file_.file_path
     self.file_.update(filename='wébextension.xpi')
     shutil.move(src, self.file_.file_path)
     self.assert_not_signed()
     signing.sign_file(self.file_)
     self.assert_signed()
Beispiel #8
0
 def sign_files(self):
     for file_ in self.files:
         if file_.is_experiment:
             ActivityLog.create(amo.LOG.EXPERIMENT_SIGNED,
                                file_,
                                user=self.user)
         sign_file(file_)
Beispiel #9
0
def migrate_legacy_dictionary_to_webextension(addon):
    """Migrate a single legacy dictionary to webextension format, creating a
    new package from the current_version, faking an upload to create a new
    Version instance."""
    user = UserProfile.objects.get(pk=settings.TASK_USER_ID)
    now = datetime.now()

    # Wrap zip in FileUpload for Version.from_upload() to consume.
    upload = FileUpload.objects.create(user=user, valid=True)

    filename = uuid.uuid4().hex + '.xpi'
    destination = os.path.join(user_media_path('addons'), 'temp', filename)
    target_language = build_webext_dictionary_from_legacy(addon, destination)
    if not addon.target_locale:
        addon.update(target_locale=target_language)

    upload.update(path=destination, name=filename)

    parsed_data = parse_addon(upload, addon=addon, user=user)
    # Create version.
    # WebExtension dictionaries are only compatible with Firefox Desktop
    # Firefox for Android uses the OS spellchecking.
    version = Version.from_upload(upload,
                                  addon,
                                  selected_apps=[amo.FIREFOX.id],
                                  channel=amo.RELEASE_CHANNEL_LISTED,
                                  parsed_data=parsed_data)
    activity.log_create(amo.LOG.ADD_VERSION, version, addon, user=user)

    # Sign the file, and set it to public. That should automatically set
    # current_version to the version we created.
    file_ = version.all_files[0]
    sign_file(file_)
    file_.update(datestatuschanged=now, reviewed=now, status=amo.STATUS_PUBLIC)
Beispiel #10
0
def new_theme_version_with_69_properties(old_version):
    timer = StopWatch(
        'addons.tasks.repack_themes_for_69.new_theme_version.')
    timer.start()

    author = get_user()
    # Wrap zip in FileUpload for Version from_upload to consume.
    upload = FileUpload.objects.create(user=author, valid=True)
    filename = uuid.uuid4().hex + '.xpi'
    destination = os.path.join(user_media_path('addons'), 'temp', filename)
    old_xpi = get_filepath(old_version.all_files[0])
    build_69_compatible_theme(
        old_xpi, destination, get_next_version_number(old_version.addon))
    upload.update(path=destination, name=filename)
    timer.log_interval('1.build_xpi')

    # Create addon + version
    parsed_data = parse_addon(upload, addon=old_version.addon, user=author)
    timer.log_interval('2.parse_addon')

    version = Version.from_upload(
        upload, old_version.addon, selected_apps=[amo.FIREFOX.id],
        channel=amo.RELEASE_CHANNEL_LISTED,
        parsed_data=parsed_data)
    timer.log_interval('3.initialize_version')

    # And finally sign the files (actually just one)
    for file_ in version.all_files:
        sign_file(file_)
        file_.update(
            reviewed=datetime.now(),
            status=amo.STATUS_APPROVED)
    timer.log_interval('4.sign_files')

    return version
Beispiel #11
0
 def test_sign_file_non_ascii_filename(self):
     src = self.file_.file_path
     self.file_.update(filename=u'jétpack.xpi')
     shutil.move(src, self.file_.file_path)
     self.assert_not_signed()
     signing.sign_file(self.file_)
     self.assert_signed()
Beispiel #12
0
def migrate_legacy_dictionary_to_webextension(addon):
    """Migrate a single legacy dictionary to webextension format, creating a
    new package from the current_version, faking an upload to create a new
    Version instance."""
    user = UserProfile.objects.get(pk=settings.TASK_USER_ID)
    now = datetime.now()

    # Wrap zip in FileUpload for Version.from_upload() to consume.
    upload = FileUpload.objects.create(
        user=user, valid=True)

    filename = uuid.uuid4().hex + '.xpi'
    destination = os.path.join(user_media_path('addons'), 'temp', filename)
    target_language = build_webext_dictionary_from_legacy(addon, destination)
    if not addon.target_locale:
        addon.update(target_locale=target_language)

    upload.update(path=destination, name=filename)

    parsed_data = parse_addon(upload, addon=addon, user=user)
    # Create version.
    # WebExtension dictionaries are only compatible with Firefox Desktop
    # Firefox for Android uses the OS spellchecking.
    version = Version.from_upload(
        upload, addon, selected_apps=[amo.FIREFOX.id],
        channel=amo.RELEASE_CHANNEL_LISTED, parsed_data=parsed_data)
    activity.log_create(amo.LOG.ADD_VERSION, version, addon, user=user)

    # Sign the file, and set it to public. That should automatically set
    # current_version to the version we created.
    file_ = version.all_files[0]
    sign_file(file_)
    file_.update(datestatuschanged=now, reviewed=now, status=amo.STATUS_PUBLIC)
Beispiel #13
0
    def test_sign_addon_with_unicode_guid(self):
        self.addon.update(guid=u'NavratnePeniaze@NávratnéPeniaze')

        signing.sign_file(self.file_)

        signature_info, manifest = _get_signature_details(
            self.file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']

        assert (
            subject_info['common_name'] ==
            u'NavratnePeniaze@NávratnéPeniaze')
        assert manifest.count('Name: ') == 3
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: V/tyfTP/mpl35QSMyq1MNfwGp/k=\n'
            'SHA256-Digest: kxvbLVQq1TerEUTNXPZjCqfU6n1BG1r2CU+TcAh4bDU=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: BofxIny3xTBMslpCxjYTq2CQMeQ=\n'
            'SHA256-Digest: mxxnbguFIZ9tT5UdvenKu5mOGV5eX4Dll1YSXp/AxvA=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n'
        )
Beispiel #14
0
    def test_call_signing_too_long_guid_bug_1203365(self):
        long_guid = 'x' * 65
        hashed = hashlib.sha256(force_bytes(long_guid)).hexdigest()
        self.addon.update(guid=long_guid)
        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == hashed
        assert manifest.count('Name: ') == 3
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: yguu1oY209BnHZkqftJFZb8UANQ=\n'
            'SHA256-Digest: BJOnqdLGdmNsM6ZE2FRFOrEUFQd2AYRlg9U/+ETXUgM=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n'
        )
    def test_sign_addon_with_unicode_guid(self):
        self.addon.update(guid=u'NavratnePeniaze@NávratnéPeniaze')

        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']

        assert (
            subject_info['common_name'] == u'NavratnePeniaze@NávratnéPeniaze')
        assert manifest.count('Name: ') == 3
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: yguu1oY209BnHZkqftJFZb8UANQ=\n'
            'SHA256-Digest: BJOnqdLGdmNsM6ZE2FRFOrEUFQd2AYRlg9U/+ETXUgM=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n')
Beispiel #16
0
    def test_supports_firefox_recent_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Recent, default to compatible.
        max_appversion.update(version='37', version_int=version_int('37'))
        self.file_.update(binary_components=False, strict_compatibility=False)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
Beispiel #17
0
 def test_sign_file(self):
     self.assert_not_signed()
     signing.sign_file(self.file_)
     self.assert_signed()
     # Make sure there's two newlines at the end of the mozilla.sf file (see
     # bug 1158938).
     with zipfile.ZipFile(self.file_.file_path, mode='r') as zf:
         with zf.open('META-INF/mozilla.sf', 'r') as mozillasf:
             assert mozillasf.read().endswith(b'\n\n')
Beispiel #18
0
 def test_sign_file(self):
     self.assert_not_signed()
     signing.sign_file(self.file_)
     self.assert_signed()
     # Make sure there's two newlines at the end of the mozilla.sf file (see
     # bug 1158938).
     with zipfile.ZipFile(self.file_.file_path, mode='r') as zf:
         with zf.open('META-INF/mozilla.sf', 'r') as mozillasf:
             assert mozillasf.read().endswith(b'\n\n')
Beispiel #19
0
    def test_supports_firefox_recent_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Recent, default to compatible.
        max_appversion.update(version='37', version_int=version_int('37'))
        self.file_.update(binary_components=False, strict_compatibility=False)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
Beispiel #20
0
    def test_supports_firefox_old_not_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Old, and not default to compatible.
        max_appversion.update(version='4', version_int=version_int('4'))
        self.file_.update(binary_components=True, strict_compatibility=True)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
Beispiel #21
0
    def test_supports_firefox_old_not_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Old, and not default to compatible.
        max_appversion.update(version='4', version_int=version_int('4'))
        self.file_.update(binary_components=True, strict_compatibility=True)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
    def process_public(self):
        """Set an add-on or a version to public."""
        # Safeguard to force implementation for unlisted add-ons to completely
        # override this method.
        assert self.version.channel == amo.RELEASE_CHANNEL_LISTED

        # Safeguard to make sure this action is not used for content review
        # (it should use confirm_auto_approved instead).
        assert not self.content_review_only

        # Sign addon.
        for file_ in self.files:
            sign_file(file_)

        # Hold onto the status before we change it.
        status = self.addon.status

        # Save files first, because set_addon checks to make sure there
        # is at least one public file or it won't make the addon public.
        self.set_files(amo.STATUS_PUBLIC, self.files)
        if self.set_addon_status:
            self.set_addon(status=amo.STATUS_PUBLIC)

        # If we've approved a webextension, add a tag identifying them as such.
        if any(file_.is_webextension for file_ in self.files):
            Tag(tag_text='firefox57').save_tag(self.addon)

        # If we've approved a mozilla signed add-on, add the firefox57 tag
        if all(file_.is_mozilla_signed_extension for file_ in self.files):
            Tag(tag_text='firefox57').save_tag(self.addon)

        # Increment approvals counter if we have a request (it means it's a
        # human doing the review) otherwise reset it as it's an automatic
        # approval.
        if self.request:
            AddonApprovalsCounter.increment_for_addon(addon=self.addon)
        else:
            AddonApprovalsCounter.reset_for_addon(addon=self.addon)

        self.log_action(amo.LOG.APPROVE_VERSION)
        template = u'%s_to_public' % self.review_type
        if self.review_type == 'pending':
            subject = u'Mozilla Add-ons: %s %s Updated'
        else:
            subject = u'Mozilla Add-ons: %s %s Approved'
        self.notify_email(template, subject)

        self.log_public_message()
        log.info(u'Sending email for %s' % (self.addon))

        # Assign reviewer incentive scores.
        if self.request:
            ReviewerScore.award_points(self.request.user,
                                       self.addon,
                                       status,
                                       version=self.version)
Beispiel #23
0
    def test_supports_firefox_android_recent_not_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Recent, not default to compatible.
        max_appversion.update(application=amo.ANDROID.id,
                              version='37', version_int=version_int('37'))
        self.file_.update(binary_components=True, strict_compatibility=True)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
    def test_supports_firefox_android_old_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Old, and default to compatible.
        max_appversion.update(application=amo.ANDROID.id,
                              version='4', version_int=version_int('4'))
        self.file_.update(binary_components=False, strict_compatibility=False)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
Beispiel #25
0
 def test_no_sign_missing_file(self):
     os.unlink(self.file_.file_path)
     assert not self.file_.is_signed
     assert not self.file_.cert_serial_num
     assert not self.file_.hash
     signing.sign_file(self.file_)
     assert not self.file_.is_signed
     assert not self.file_.cert_serial_num
     assert not self.file_.hash
     assert not signing.is_signed(self.file_.file_path)
Beispiel #26
0
    def test_supports_firefox_android_old_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Old, and default to compatible.
        max_appversion.update(application=amo.ANDROID.id,
                              version='4', version_int=version_int('4'))
        self.file_.update(binary_components=False, strict_compatibility=False)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
    def test_supports_firefox_android_recent_not_default_to_compatible(self):
        max_appversion = self.version.apps.first().max

        # Recent, not default to compatible.
        max_appversion.update(application=amo.ANDROID.id,
                              version='37', version_int=version_int('37'))
        self.file_.update(binary_components=True, strict_compatibility=True)
        self.assert_not_signed()
        signing.sign_file(self.file_)
        self.assert_signed()
Beispiel #28
0
 def test_no_sign_missing_file(self):
     os.unlink(self.file_.file_path)
     assert not self.file_.is_signed
     assert not self.file_.cert_serial_num
     assert not self.file_.hash
     with self.assertRaises(signing.SigningError):
         signing.sign_file(self.file_)
     assert not self.file_.is_signed
     assert not self.file_.cert_serial_num
     assert not self.file_.hash
     assert not signing.is_signed(self.file_.file_path)
Beispiel #29
0
    def test_sign_file_with_utf8_filename_inside_package(self):
        fpath = 'src/olympia/files/fixtures/files/unicode-filenames.xpi'
        with amo.tests.copy_file(fpath, self.file_.file_path, overwrite=True):
            self.assert_not_signed()
            signing.sign_file(self.file_)
            self.assert_signed()

            with zipfile.ZipFile(self.file_.file_path, mode='r') as zf:
                with zf.open('META-INF/manifest.mf', 'r') as manifest_mf:
                    manifest_contents = manifest_mf.read().decode('utf-8')
                    assert u'\u1109\u1161\u11a9' in manifest_contents
Beispiel #30
0
 def test_no_sign_missing_file(self):
     os.unlink(self.file_.file_path)
     assert not self.file_.is_signed
     assert not self.file_.cert_serial_num
     assert not self.file_.hash
     with self.assertRaises(signing.SigningError):
         signing.sign_file(self.file_)
     assert not self.file_.is_signed
     assert not self.file_.cert_serial_num
     assert not self.file_.hash
     assert not signing.is_signed(self.file_.file_path)
Beispiel #31
0
    def test_sign_file_with_utf8_filename_inside_package(self):
        fpath = 'src/olympia/files/fixtures/files/unicode-filenames.xpi'
        with amo.tests.copy_file(fpath, self.file_.file_path, overwrite=True):
            self.assert_not_signed()
            signing.sign_file(self.file_)
            self.assert_signed()

            with zipfile.ZipFile(self.file_.file_path, mode='r') as zf:
                with zf.open('META-INF/manifest.mf', 'r') as manifest_mf:
                    manifest_contents = manifest_mf.read().decode('utf-8')
                    assert '\u1109\u1161\u11a9' in manifest_contents
Beispiel #32
0
    def process_public(self):
        """Set an add-on or a version to public."""
        # Safeguard to force implementation for unlisted add-ons to completely
        # override this method.
        assert self.version.channel == amo.RELEASE_CHANNEL_LISTED

        # Safeguard to make sure this action is not used for content review
        # (it should use confirm_auto_approved instead).
        assert not self.content_review_only

        # Sign addon.
        for file_ in self.files:
            sign_file(file_)

        # Hold onto the status before we change it.
        status = self.addon.status

        # Save files first, because set_addon checks to make sure there
        # is at least one public file or it won't make the addon public.
        self.set_files(amo.STATUS_APPROVED, self.files)
        self.set_recommended()
        if self.set_addon_status:
            self.set_addon(status=amo.STATUS_APPROVED)

        # Clear needs_human_review flags on past listed versions.
        if self.human_review:
            self.unset_past_needs_human_review()

        # Increment approvals counter if we have a request (it means it's a
        # human doing the review) otherwise reset it as it's an automatic
        # approval.
        if self.human_review:
            AddonApprovalsCounter.increment_for_addon(addon=self.addon)
        else:
            AddonApprovalsCounter.reset_for_addon(addon=self.addon)

        self.log_action(amo.LOG.APPROVE_VERSION)
        template = u'%s_to_approved' % self.review_type
        if self.review_type in ['extension_pending', 'theme_pending']:
            subject = u'Mozilla Add-ons: %s %s Updated'
        else:
            subject = u'Mozilla Add-ons: %s %s Approved'
        self.notify_email(template, subject)

        self.log_public_message()
        log.info(u'Sending email for %s' % (self.addon))

        # Assign reviewer incentive scores.
        if self.human_review:
            ReviewerScore.award_points(self.user,
                                       self.addon,
                                       status,
                                       version=self.version)
Beispiel #33
0
    def test_call_signing_recommended_addon(self):
        # Mark the add-on as recommended
        DiscoveryItem.objects.create(addon=self.file_.version.addon,
                                     recommendable=True)

        # And also set the required flags on the version as done during
        # approval.
        self.file_.version.update(recommendation_approved=True)

        assert signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == 'xxxxx'
        assert manifest.count('Name: ') == 4

        assert 'Name: mozilla-recommendation.json' in manifest
        assert 'Name: install.rdf' in manifest
        assert 'Name: META-INF/cose.manifest' in manifest
        assert 'Name: META-INF/cose.sig' in manifest

        recommendation_data = self._get_recommendation_data()
        assert recommendation_data['addon_id'] == 'xxxxx'
        assert recommendation_data['states'] == ['recommended']
    def test_call_signing_recommended(self):
        # This is the usual process for recommended add-ons, they're
        # in "pending recommendation" and only *after* we approve and sign
        # them they will become "recommended". Once the `recommendable`
        # flag is turned off we won't sign further versions as recommended.
        DiscoveryItem.objects.create(
            addon=self.file_.version.addon,
            recommendable=True)

        assert signing.sign_file(self.file_)

        signature_info, manifest = _get_signature_details(
            self.file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == 'xxxxx'
        assert manifest.count('Name: ') == 5

        assert 'Name: mozilla-recommendation.json' in manifest
        assert 'Name: manifest.json' in manifest
        assert 'Name: META-INF/cose.manifest' in manifest
        assert 'Name: META-INF/cose.sig' in manifest

        recommendation_data = _get_recommendation_data(
            self.file_.current_file_path)
        assert recommendation_data['addon_id'] == 'xxxxx'
        assert recommendation_data['states'] == ['recommended']
Beispiel #35
0
    def test_call_signing_add_guid(self):
        file_ = version_factory(
            addon=self.addon, file_kw={'filename': 'webextension_no_id.xpi'}
        ).file
        assert signing.sign_file(file_)

        signature_info, manifest = _get_signature_details(file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == 'xxxxx'
        assert manifest.count('Name: ') == 4
        # Need to use .startswith() since the signature from `cose.sig`
        # changes on every test-run, so we're just not going to check it
        # explicitly...
        assert manifest.startswith(
            'Manifest-Version: 1.0\n\n'
            'Name: README.md\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: MAajMoNW9rYdgU0VwiTJxfh9TF0=\n'
            'SHA256-Digest: Dj3HrJ4QDG5YPGff4YsjSqAVYKU99f3vz1ssno2Cloc=\n\n'
            'Name: manifest.json\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: 77nz8cVnruIKyRPRqnIfao1uoHw=\n'
            'SHA256-Digest: m0f3srI8vw15H8mYbQlb+adptxNt2QGXT69krfoq+T0=\n\n'
            'Name: META-INF/cose.manifest\n'
            'Digest-Algorithms: SHA1 SHA256\n'
            'SHA1-Digest: 6IHHewfjdgEaJGfD86oqWo1qel0=\n'
            'SHA256-Digest: bExoReutlIoZMatxIQ4jtgAyujR1q193Ng0tjooB2Hc=\n\n'
            'Name: META-INF/cose.sig\n'
            'Digest-Algorithms: SHA1 SHA256\n'
        )
Beispiel #36
0
    def process_public(self):
        """Set an add-on or a version to public."""
        # Safeguard to force implementation for unlisted add-ons to completely
        # override this method.
        assert self.version.channel == amo.RELEASE_CHANNEL_LISTED

        # Safeguard to make sure this action is not used for content review
        # (it should use confirm_auto_approved instead).
        assert not self.content_review_only

        # Sign addon.
        for file_ in self.files:
            sign_file(file_)

        # Hold onto the status before we change it.
        status = self.addon.status

        # Save files first, because set_addon checks to make sure there
        # is at least one public file or it won't make the addon public.
        self.set_files(amo.STATUS_APPROVED, self.files)
        self.set_recommended()
        if self.set_addon_status:
            self.set_addon(status=amo.STATUS_APPROVED)

        # Increment approvals counter if we have a request (it means it's a
        # human doing the review) otherwise reset it as it's an automatic
        # approval.
        if self.request:
            AddonApprovalsCounter.increment_for_addon(addon=self.addon)
        else:
            AddonApprovalsCounter.reset_for_addon(addon=self.addon)

        self.log_action(amo.LOG.APPROVE_VERSION)
        template = u'%s_to_approved' % self.review_type
        if self.review_type in ['extension_pending', 'theme_pending']:
            subject = u'Mozilla Add-ons: %s %s Updated'
        else:
            subject = u'Mozilla Add-ons: %s %s Approved'
        self.notify_email(template, subject)

        self.log_public_message()
        log.info(u'Sending email for %s' % (self.addon))

        # Assign reviewer incentive scores.
        if self.request:
            ReviewerScore.award_points(
                self.request.user, self.addon, status, version=self.version)
    def test_runs_git_extraction_after_signing(self):
        old_git_hash = self.version.git_hash

        with transaction.atomic():
            signing.sign_file(self.version.current_file)

        self.version.refresh_from_db()
        assert self.version.git_hash != old_git_hash

        repo = AddonGitRepository(self.addon)

        output = _run_process('git log listed', repo)
        assert output.count('Create new version') == 2
        assert '(after successful signing)' in output

        # 2 actual commits, including the repo initialization
        assert output.count('Mozilla Add-ons Robot') == 3
    def test_call_signing_too_long_guid_bug_1203365(self):
        long_guid = 'x' * 65
        hashed = hashlib.sha256(force_bytes(long_guid)).hexdigest()
        self.addon.update(guid=long_guid)
        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == hashed
        assert manifest == (
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: MD5 SHA1 SHA256\n'
            'MD5-Digest: AtjchjiOU/jDRLwMx214hQ==\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n')
Beispiel #39
0
    def test_call_signing_too_long_guid_bug_1203365(self):
        long_guid = 'x' * 65
        hashed = hashlib.sha256(force_bytes(long_guid)).hexdigest()
        self.addon.update(guid=long_guid)
        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == hashed
        assert manifest == (
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: MD5 SHA1 SHA256\n'
            'MD5-Digest: AtjchjiOU/jDRLwMx214hQ==\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n'
        )
    def test_sign_addon_with_unicode_guid(self):
        self.addon.update(guid=u'NavratnePeniaze@NávratnéPeniaze')

        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']

        assert (
            subject_info['common_name'] == u'NavratnePeniaze@NávratnéPeniaze')
        assert manifest == (
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: MD5 SHA1 SHA256\n'
            'MD5-Digest: AtjchjiOU/jDRLwMx214hQ==\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n')
Beispiel #41
0
    def process_public(self):
        """Set an unlisted addon version files to public."""
        assert self.version.channel == amo.RELEASE_CHANNEL_UNLISTED

        # Sign addon.
        for file_ in self.files:
            sign_file(file_)

        self.set_files(amo.STATUS_PUBLIC, self.files)

        template = u'unlisted_to_reviewed_auto'
        subject = u'Mozilla Add-ons: %s %s signed and ready to download'
        self.log_action(amo.LOG.APPROVE_VERSION)

        self.notify_email(template, subject, perm_setting=None)

        log.info(u'Making %s files %s public' %
                 (self.addon, ', '.join([f.filename for f in self.files])))
        log.info(u'Sending email for %s' % (self.addon))
Beispiel #42
0
    def test_sign_addon_with_unicode_guid(self):
        self.addon.update(guid=u'NavratnePeniaze@NávratnéPeniaze')

        signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']

        assert (
            subject_info['common_name'] ==
            u'NavratnePeniaze@NávratnéPeniaze')
        assert manifest == (
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: MD5 SHA1 SHA256\n'
            'MD5-Digest: AtjchjiOU/jDRLwMx214hQ==\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n')
Beispiel #43
0
    def process_public(self):
        """Set an unlisted addon version files to public."""
        assert self.version.channel == amo.RELEASE_CHANNEL_UNLISTED

        # Sign addon.
        for file_ in self.files:
            sign_file(file_)

        self.set_files(amo.STATUS_APPROVED, self.files)

        template = u'unlisted_to_reviewed_auto'
        subject = u'Mozilla Add-ons: %s %s signed and ready to download'
        self.log_action(amo.LOG.APPROVE_VERSION)

        self.notify_email(template, subject, perm_setting=None)

        log.info(u'Making %s files %s public' %
                 (self.addon, ', '.join([f.filename for f in self.files])))
        log.info(u'Sending email for %s' % (self.addon))
Beispiel #44
0
    def test_runs_git_extraction_after_signing(self):
        # Make sure the initial version is already extracted, simulating
        # a regular upload.
        AddonGitRepository.extract_and_commit_from_version(self.version)
        self.version.refresh_from_db()

        old_git_hash = self.version.git_hash

        signing.sign_file(self.file_)

        self.version.refresh_from_db()
        assert self.version.git_hash != old_git_hash

        repo = AddonGitRepository(self.addon)

        output = _run_process('git log listed', repo)
        assert output.count('Create new version') == 2
        assert '(after successful signing)' in output

        # 2 actual commits, including the repo initialization
        assert output.count('Mozilla Add-ons Robot') == 3
Beispiel #45
0
    def test_call_signing_not_recommendable(self):
        PromotedAddon.objects.create(addon=self.file_.version.addon)

        assert signing.sign_file(self.file_)

        signature_info, manifest = _get_signature_details(
            self.file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == 'xxxxx'
        assert manifest.count('Name: ') == 4

        assert 'Name: mozilla-recommendation.json' not in manifest
Beispiel #46
0
    def test_call_signing_promoted_no_special_autograph_group(self):
        # SPOTLIGHT addons aren't signed differently.
        self.make_addon_promoted(self.file_.version.addon, SPOTLIGHT)

        assert signing.sign_file(self.file_)

        signature_info, manifest = _get_signature_details(self.file_.current_file_path)

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == 'xxxxx'
        assert manifest.count('Name: ') == 4

        assert 'Name: mozilla-recommendation.json' not in manifest
Beispiel #47
0
    def test_call_signing(self):
        assert signing.sign_file(self.file_)

        signature_info, manifest = self._get_signature_details()

        subject_info = signature_info.signer_certificate['subject']
        assert subject_info['common_name'] == 'xxxxx'
        assert manifest == (
            'Manifest-Version: 1.0\n\n'
            'Name: install.rdf\n'
            'Digest-Algorithms: MD5 SHA1 SHA256\n'
            'MD5-Digest: AtjchjiOU/jDRLwMx214hQ==\n'
            'SHA1-Digest: W9kwfZrvMkbgjOx6nDdibCNuCjk=\n'
            'SHA256-Digest: 3Wjjho1pKD/9VaK+FszzvZFN/2crBmaWbdisLovwo6g=\n\n'
        )
Beispiel #48
0
def add_static_theme_from_lwt(lwt):
    from olympia.activity.models import AddonLog

    timer = StopWatch(
        'addons.tasks.migrate_lwts_to_static_theme.add_from_lwt.')
    timer.start()

    olympia.core.set_user(UserProfile.objects.get(pk=settings.TASK_USER_ID))
    # Try to handle LWT with no authors
    author = (lwt.listed_authors or [_get_lwt_default_author()])[0]
    # Wrap zip in FileUpload for Addon/Version from_upload to consume.
    upload = FileUpload.objects.create(
        user=author, valid=True)
    filename = uuid.uuid4().hex + '.xpi'
    destination = os.path.join(user_media_path('addons'), 'temp', filename)
    build_static_theme_xpi_from_lwt(lwt, destination)
    upload.update(path=destination, name=filename)
    timer.log_interval('1.build_xpi')

    # Create addon + version
    parsed_data = parse_addon(upload, user=author)
    timer.log_interval('2a.parse_addon')

    addon = Addon.initialize_addon_from_upload(
        parsed_data, upload, amo.RELEASE_CHANNEL_LISTED, author)
    addon_updates = {}
    timer.log_interval('2b.initialize_addon')

    # static themes are only compatible with Firefox at the moment,
    # not Android
    version = Version.from_upload(
        upload, addon, selected_apps=[amo.FIREFOX.id],
        channel=amo.RELEASE_CHANNEL_LISTED,
        parsed_data=parsed_data)
    timer.log_interval('3.initialize_version')

    # Set category
    lwt_category = (lwt.categories.all() or [None])[0]  # lwt only have 1 cat.
    lwt_category_slug = lwt_category.slug if lwt_category else 'other'
    for app, type_dict in CATEGORIES.items():
        static_theme_categories = type_dict.get(amo.ADDON_STATICTHEME, [])
        static_category = static_theme_categories.get(
            lwt_category_slug, static_theme_categories.get('other'))
        AddonCategory.objects.create(
            addon=addon,
            category=Category.from_static_category(static_category, True))
    timer.log_interval('4.set_categories')

    # Set license
    lwt_license = PERSONA_LICENSES_IDS.get(
        lwt.persona.license, LICENSE_COPYRIGHT_AR)  # default to full copyright
    static_license = License.objects.get(builtin=lwt_license.builtin)
    version.update(license=static_license)
    timer.log_interval('5.set_license')

    # Set tags
    for addon_tag in AddonTag.objects.filter(addon=lwt):
        AddonTag.objects.create(addon=addon, tag=addon_tag.tag)
    timer.log_interval('6.set_tags')

    # Steal the ratings (even with soft delete they'll be deleted anyway)
    addon_updates.update(
        average_rating=lwt.average_rating,
        bayesian_rating=lwt.bayesian_rating,
        total_ratings=lwt.total_ratings,
        text_ratings_count=lwt.text_ratings_count)
    Rating.unfiltered.filter(addon=lwt).update(addon=addon, version=version)
    timer.log_interval('7.move_ratings')

    # Replace the lwt in collections
    CollectionAddon.objects.filter(addon=lwt).update(addon=addon)

    # Modify the activity log entry too.
    rating_activity_log_ids = [
        l.id for l in amo.LOG if getattr(l, 'action_class', '') == 'review']
    addonlog_qs = AddonLog.objects.filter(
        addon=lwt, activity_log__action__in=rating_activity_log_ids)
    [alog.transfer(addon) for alog in addonlog_qs.iterator()]
    timer.log_interval('8.move_activity_logs')

    # Copy the ADU statistics - the raw(ish) daily UpdateCounts for stats
    # dashboard and future update counts, and copy the average_daily_users.
    # hotness will be recalculated by the deliver_hotness() cron in a more
    # reliable way that we could do, so skip it entirely.
    migrate_theme_update_count(lwt, addon)
    addon_updates.update(
        average_daily_users=lwt.persona.popularity or 0,
        hotness=0)
    timer.log_interval('9.copy_statistics')

    # Logging
    activity.log_create(
        amo.LOG.CREATE_STATICTHEME_FROM_PERSONA, addon, user=author)

    # And finally sign the files (actually just one)
    for file_ in version.all_files:
        sign_file(file_)
        file_.update(
            datestatuschanged=lwt.last_updated,
            reviewed=datetime.now(),
            status=amo.STATUS_APPROVED)
    timer.log_interval('10.sign_files')
    addon_updates['status'] = amo.STATUS_APPROVED

    # set the modified and creation dates to match the original.
    addon_updates['created'] = lwt.created
    addon_updates['modified'] = lwt.modified
    addon_updates['last_updated'] = lwt.last_updated

    addon.update(**addon_updates)
    return addon
Beispiel #49
0
 def test_dont_sign_search_plugins(self):
     self.addon.update(type=amo.ADDON_SEARCH)
     self.file_.update(is_webextension=False)
     signing.sign_file(self.file_)
     self.assert_not_signed()
Beispiel #50
0
 def test_dont_sign_again_mozilla_signed_extensions(self):
     """Don't try to resign mozilla signed extensions."""
     self.file_.update(is_mozilla_signed_extension=True)
     signing.sign_file(self.file_)
     self.assert_not_signed()
Beispiel #51
0
 def test_is_signed(self):
     assert not signing.is_signed(self.file_.file_path)
     signing.sign_file(self.file_)
     assert signing.is_signed(self.file_.file_path)
Beispiel #52
0
 def test_size_updated(self):
     unsigned_size = storage.size(self.file_.file_path)
     signing.sign_file(self.file_)
     signed_size = storage.size(self.file_.file_path)
     assert self.file_.size == signed_size
     assert unsigned_size < signed_size
Beispiel #53
0
def sign_addons(addon_ids, force=False, **kw):
    """Used to sign all the versions of an addon.

    This is used in the 'process_addons --task sign_addons' management
    command.

    It also bumps the version number of the file and the Version, so the
    Firefox extension update mechanism picks this new signed version and
    installs it.
    """
    log.info(u'[{0}] Signing addons.'.format(len(addon_ids)))

    reasons = {
        'default': [MAIL_SUBJECT, MAIL_MESSAGE],
        'expiry': [MAIL_EXPIRY_SUBJECT, MAIL_EXPIRY_MESSAGE]
    }
    mail_subject, mail_message = reasons[kw.get('reason', 'default')]

    addons_emailed = set()
    # We only care about extensions.
    for version in Version.objects.filter(addon_id__in=addon_ids,
                                          addon__type=amo.ADDON_EXTENSION):
        # We only sign files that have been reviewed and are compatible with
        # versions of Firefox that are recent enough.
        to_sign = version.files.filter(
            version__apps__max__application__in=SIGN_FOR_APPS,
            status__in=amo.REVIEWED_STATUSES)

        if force:
            to_sign = to_sign.all()
        else:
            to_sign = to_sign.filter(is_signed=False)
        if not to_sign:
            log.info(u'Not signing addon {0}, version {1} (no files or already'
                     u' signed)'.format(version.addon, version))
        log.info(u'Signing addon {0}, version {1}'.format(version.addon,
                                                          version))
        bumped_version_number = get_new_version_number(version.version)
        signed_at_least_a_file = False  # Did we sign at least one file?
        for file_obj in to_sign:
            if not os.path.isfile(file_obj.file_path):
                log.info(u'File {0} does not exist, skip'.format(file_obj.pk))
                continue
            # Save the original file, before bumping the version.
            backup_path = u'{0}.backup_signature'.format(file_obj.file_path)
            shutil.copy(file_obj.file_path, backup_path)
            try:
                # Need to bump the version (modify manifest file)
                # before the file is signed.
                update_version_number(file_obj, bumped_version_number)
                signed = bool(sign_file(file_obj))
                if signed:  # Bump the version number if at least one signed.
                    signed_at_least_a_file = True
                else:  # We didn't sign, so revert the version bump.
                    shutil.move(backup_path, file_obj.file_path)
            except Exception:
                log.error(u'Failed signing file {0}'.format(file_obj.pk),
                          exc_info=True)
                # Revert the version bump, restore the backup.
                shutil.move(backup_path, file_obj.file_path)
        # Now update the Version model, if we signed at least one file.
        if signed_at_least_a_file:
            version.update(version=bumped_version_number,
                           version_int=version_int(bumped_version_number))
            addon = version.addon
            if addon.pk not in addons_emailed:
                # Send a mail to the owners/devs warning them we've
                # automatically signed their addon.
                qs = (AddonUser.objects
                      .filter(role=amo.AUTHOR_ROLE_OWNER, addon=addon)
                      .exclude(user__email__isnull=True))
                emails = qs.values_list('user__email', flat=True)
                subject = mail_subject.format(addon=addon.name)
                message = mail_message.format(
                    addon=addon.name,
                    addon_url=amo.templatetags.jinja_helpers.absolutify(
                        addon.get_dev_url(action='versions')))
                amo.utils.send_mail(
                    subject, message, recipient_list=emails,
                    headers={'Reply-To': '*****@*****.**'})
                addons_emailed.add(addon.pk)
Beispiel #54
0
 def _sign_file(self, file_):
     signing.sign_file(file_)