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')
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' )
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' )
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
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_)
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()
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_)
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)
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
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()
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)
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' )
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')
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()
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')
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')
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()
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 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)
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()
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)
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()
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)
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
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)
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
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)
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']
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' )
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')
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')
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))
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')
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))
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
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
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
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' )
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
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()
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()
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)
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
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)
def _sign_file(self, file_): signing.sign_file(file_)