Esempio n. 1
0
 def test_bool(self):
     # bool(VersionString(x)) should behave like bool(x)
     assert bool(VersionString('')) is False
     assert bool(VersionString('0')) is True
     assert bool(VersionString(0)) is True
     assert bool(VersionString('false')) is True
     assert bool(VersionString('False')) is True
     assert bool(VersionString('something')) is True
     assert bool(VersionString('3.6.*')) is True
     assert bool(VersionString('3.6')) is True
     assert bool(VersionString('*')) is True
Esempio n. 2
0
 def test_part_indexing(self):
     vs = VersionString('32.6pre9')
     assert vs['major'] == 32
     assert vs['minor3'] == 0
     assert vs['alpha'] == ''
     assert vs['pre'] == 'pre'
     assert vs['pre_ver'] == 9
     assert vs[0] == '3'  # normal string indexing still works
     assert vs[3:5] == '6p'
Esempio n. 3
0
 def test_parts(self):
     assert tuple(VersionString('3.6a5pre9').parts) == (
         ('major', 3),
         ('minor1', 6),
         ('minor2', 0),
         ('minor3', 0),
         ('alpha', 'a'),
         ('alpha_ver', 5),
         ('pre', 'pre'),
         ('pre_ver', 9),
     )
     assert tuple(VersionString('3.6.*').parts) == (
         ('major', 3),
         ('minor1', 6),
         ('minor2', '*'),
         ('minor3', '*'),
         ('alpha', ''),
         ('alpha_ver', 0),
         ('pre', ''),
         ('pre_ver', 0),
     )
Esempio n. 4
0
def get_simple_version(version_string):
    """Extract the version number without the ><= requirements, returning a
    VersionString instance.

    This simply extracts the version number without the ><= requirement so
    it will not be accurate for version requirements that are not >=, <= or
    = to a version.

    >>> get_simple_version('>=33.0a1')
    VersionString('33.0a1')
    """
    if not version_string:
        version_string = ''
    return VersionString(re.sub('[<=>]', '', version_string))
Esempio n. 5
0
 def test_comparison(self):
     assert VersionString('3.6.*') > VersionString('3.6.8')
     assert VersionString('3.6.*') > VersionString('3.6.65535')
     assert VersionString('*') > VersionString('65535.0.0.1')
     assert VersionString('*') > VersionString('65536.65536.65536.65536')
     assert VersionString('*') > VersionString('98.*')
     assert VersionString('98.*') < VersionString('*')
     assert VersionString('65534.*') < VersionString('*')
     assert VersionString('5.*') > VersionString('5.0.*')
     assert VersionString('3.6a5pre9') < VersionString('3.6')
     assert VersionString('3.6a5pre9') < VersionString('3.6b1')
     assert VersionString('3.6.*') > VersionString('3.6a5pre9')
     assert VersionString('99.99999999b1') > VersionString('99.99999998b1')
     assert VersionString('99999999.99b1') > VersionString('99999998.99b1')
     assert VersionString('*') > VersionString('99999998.99b1')
Esempio n. 6
0
 def test_equality(self):
     assert VersionString('3.6.0.0') == VersionString('3.6')
     assert VersionString('3.6.*.0') != VersionString('3.6.*')
     assert VersionString('*') == VersionString('*.*.*.*')
     assert VersionString('*.0.0.0') != VersionString('65535')
     assert VersionString('3.6.*') != VersionString('3.6.65535')
     assert VersionString('*') != VersionString('65535.65535.65535.65535')
     assert VersionString('*') != VersionString('65535.0.0.0')
     assert VersionString('3.6a5pre9') != VersionString('3.6')
     # edge cases with falsey values
     assert VersionString('0') != ''
     assert VersionString('') != '0'
     assert VersionString('0') is not None
     assert VersionString('') is not None
     none = None
     assert VersionString('0') != none
     assert VersionString('') != none
Esempio n. 7
0
class Block(ModelBase):
    MIN = VersionString('0')
    MAX = VersionString('*')

    guid = models.CharField(max_length=255, unique=True, null=False)
    min_version = VersionStringField(max_length=255,
                                     blank=False,
                                     default=MIN,
                                     validators=(no_asterisk, ))
    max_version = VersionStringField(max_length=255, blank=False, default=MAX)
    url = models.CharField(max_length=255, blank=True)
    reason = models.TextField(blank=True)
    updated_by = models.ForeignKey(UserProfile,
                                   null=True,
                                   on_delete=models.SET_NULL)
    legacy_id = models.CharField(max_length=255,
                                 null=False,
                                 default='',
                                 db_index=True,
                                 db_column='kinto_id')
    submission = models.ManyToManyField('BlocklistSubmission')
    average_daily_users_snapshot = models.IntegerField(null=True)

    ACTIVITY_IDS = (
        amo.LOG.BLOCKLIST_BLOCK_ADDED.id,
        amo.LOG.BLOCKLIST_BLOCK_EDITED.id,
        amo.LOG.BLOCKLIST_BLOCK_DELETED.id,
        amo.LOG.BLOCKLIST_SIGNOFF.id,
    )

    def __str__(self):
        return f'Block: {self.guid}'

    def __init__(self, *args, **kwargs):
        # Optimized case of creating a Block from Addon so skipping the query.
        addon = kwargs.pop('addon', None)
        if addon:
            kwargs['guid'] = addon.guid
            self.addon = addon
        super().__init__(*args, **kwargs)

    def save(self, **kwargs):
        assert self.updated_by
        return super().save(**kwargs)

    @property
    def is_imported_from_legacy_regex(self):
        return self.legacy_id.startswith('*')

    @property
    def in_legacy_blocklist(self):
        return bool(self.legacy_id)

    @classmethod
    def get_addons_for_guids_qs(cls, guids):
        return (Addon.unfiltered.filter(
            guid__in=guids).order_by('-id').only_translations())

    @cached_property
    def addon(self):
        return self.get_addons_for_guids_qs((self.guid, )).first()

    @property
    def current_adu(self):
        return self.addon.average_daily_users if self.addon else 0

    @cached_property
    def addon_versions(self):
        # preload_addon_versions will overwrite this cached_property.
        self.preload_addon_versions([self])
        return self.addon_versions

    @classmethod
    def preload_addon_versions(cls, blocks):
        """Preload block.addon_versions into a list of blocks."""
        block_guids = [block.guid for block in blocks]
        GUID = 'addon__addonguid__guid'
        qs = (Version.unfiltered.filter(**{
            f'{GUID}__in': block_guids
        }).order_by('id').no_transforms().annotate(**{GUID: models.F(GUID)}))

        all_addon_versions = defaultdict(list)
        for version in qs:
            all_addon_versions[getattr(version, GUID)].append(version)
        for block in blocks:
            block.addon_versions = all_addon_versions[block.guid]

    def clean(self):
        if self.id:
            # We're only concerned with edits - self.guid isn't set at this
            # point for new instances anyway.
            choices = list(version.version for version in self.addon_versions)
            if self.min_version not in choices + [self.MIN]:
                raise ValidationError({'min_version': _('Invalid version')})
            if self.max_version not in choices + [self.MAX]:
                raise ValidationError({'max_version': _('Invalid version')})
        if self.min_version > self.max_version:
            raise ValidationError(
                _('Min version can not be greater than Max version'))

    def is_version_blocked(self, version):
        return self.min_version <= version and self.max_version >= version

    def review_listed_link(self):
        has_listed = any(True for version in self.addon_versions
                         if version.channel == amo.RELEASE_CHANNEL_LISTED)
        if has_listed:
            url = absolutify(
                reverse('reviewers.review', kwargs={'addon_id':
                                                    self.addon.pk}))
            return format_html('<a href="{}">{}</a>', url, _('Review Listed'))
        return ''

    def review_unlisted_link(self):
        has_unlisted = any(True for version in self.addon_versions
                           if version.channel == amo.RELEASE_CHANNEL_UNLISTED)
        if has_unlisted:
            url = absolutify(
                reverse('reviewers.review', args=('unlisted', self.addon.pk)))
            return format_html('<a href="{}">{}</a>', url,
                               _('Review Unlisted'))
        return ''

    @cached_property
    def active_submissions(self):
        return BlocklistSubmission.get_submissions_from_guid(self.guid)

    @property
    def is_readonly(self):
        legacy_submit_off = not waffle.switch_is_active(
            'blocklist_legacy_submit')
        return (legacy_submit_off
                and self.legacy_id) or self.active_submissions

    @classmethod
    def get_blocks_from_guids(cls, guids):
        """Given a list of guids, return a list of Blocks - either existing
        instances if the guid exists in a Block, or new instances otherwise.
        """
        # load all the Addon instances together
        using_db = get_replica()
        addons = list(cls.get_addons_for_guids_qs(guids).using(using_db))

        # And then any existing block instances
        existing_blocks = {
            block.guid: block
            for block in cls.objects.using(using_db).filter(guid__in=guids)
        }

        for addon in addons:
            # get the existing block object or create a new instance
            block = existing_blocks.get(addon.guid, None)
            if block:
                # if it exists hook up the addon instance
                block.addon = addon
            else:
                # otherwise create a new Block
                block = Block(addon=addon)
                existing_blocks[block.guid] = block
        return list(existing_blocks.values())
Esempio n. 8
0
    def apps(self):
        """Get `AppVersion`s for the application."""
        type_ = self.type
        if type_ == amo.ADDON_LPAPP:
            # Langpack are only compatible with Firefox desktop at the moment.
            # https://github.com/mozilla/addons-server/issues/8381
            # They are all strictly compatible with a specific version, so
            # the default min version here doesn't matter much.
            apps = ((amo.FIREFOX, amo.DEFAULT_WEBEXT_MIN_VERSION), )
        elif type_ == amo.ADDON_STATICTHEME:
            # Static themes are only compatible with Firefox desktop >= 53
            # and Firefox for Android >=65.
            apps = (
                (amo.FIREFOX, amo.DEFAULT_STATIC_THEME_MIN_VERSION_FIREFOX),
                (amo.ANDROID, amo.DEFAULT_STATIC_THEME_MIN_VERSION_ANDROID),
            )
        elif type_ == amo.ADDON_DICT:
            # WebExt dicts are only compatible with Firefox desktop >= 61.
            apps = ((amo.FIREFOX,
                     amo.DEFAULT_WEBEXT_DICT_MIN_VERSION_FIREFOX), )
        else:
            webext_min = (amo.DEFAULT_WEBEXT_MIN_VERSION if
                          self.get('browser_specific_settings', None) is None
                          else amo.DEFAULT_WEBEXT_MIN_VERSION_BROWSER_SPECIFIC)
            # amo.DEFAULT_WEBEXT_MIN_VERSION_BROWSER_SPECIFIC should be 48.0,
            # which is the same as amo.DEFAULT_WEBEXT_MIN_VERSION_ANDROID, so
            # no specific treatment for Android.
            apps = (
                (amo.FIREFOX, webext_min),
                (amo.ANDROID, amo.DEFAULT_WEBEXT_MIN_VERSION_ANDROID),
            )

        if self.get('manifest_version') == 3:
            # Update minimum supported versions if it's an mv3 addon.
            mv3_mins = {
                amo.FIREFOX: amo.DEFAULT_WEBEXT_MIN_VERSION_MV3_FIREFOX,
                amo.ANDROID: amo.DEFAULT_WEBEXT_MIN_VERSION_MV3_ANDROID,
            }
            apps = ((app,
                     max(VersionString(ver),
                         mv3_mins.get(app, mv3_mins[amo.FIREFOX])))
                    for app, ver in apps)

        doesnt_support_no_id = (self.strict_min_version
                                and self.strict_min_version < VersionString(
                                    amo.DEFAULT_WEBEXT_MIN_VERSION_NO_ID))

        if self.guid is None and doesnt_support_no_id:
            raise forms.ValidationError(
                gettext('Add-on ID is required for Firefox 47 and below.'))

        # If a minimum strict version is specified, it needs to be higher
        # than the version when Firefox started supporting WebExtensions.
        unsupported_no_matter_what = (self.strict_min_version and
                                      self.strict_min_version < VersionString(
                                          amo.DEFAULT_WEBEXT_MIN_VERSION))
        if unsupported_no_matter_what:
            msg = gettext('Lowest supported "strict_min_version" is 42.0.')
            raise forms.ValidationError(msg)

        for app, default_min_version in apps:
            if self.guid is None and not self.strict_min_version:
                strict_min_version = max(
                    VersionString(amo.DEFAULT_WEBEXT_MIN_VERSION_NO_ID),
                    VersionString(default_min_version),
                )
            else:
                # strict_min_version for this app shouldn't be lower than the
                # default min version for this app.
                strict_min_version = max(self.strict_min_version,
                                         VersionString(default_min_version))

            strict_max_version = self.strict_max_version or VersionString(
                amo.DEFAULT_WEBEXT_MAX_VERSION)

            if strict_max_version < strict_min_version:
                strict_max_version = strict_min_version

            qs = AppVersion.objects.filter(application=app.id)
            try:
                min_appver = qs.get(version=strict_min_version)
            except AppVersion.DoesNotExist:
                msg = gettext(
                    'Unknown "strict_min_version" {appver} for {app}'.format(
                        app=app.pretty, appver=strict_min_version))
                raise forms.ValidationError(msg)

            try:
                max_appver = qs.get(version=strict_max_version)
            except AppVersion.DoesNotExist:
                # If the specified strict_max_version can't be found, raise an
                # error: we used to use '*' instead but this caused more
                # problems, especially with langpacks that are really specific
                # to a given Firefox version.
                msg = gettext(
                    'Unknown "strict_max_version" {appver} for {app}'.format(
                        app=app.pretty, appver=strict_max_version))
                raise forms.ValidationError(msg)

            yield Extractor.App(appdata=app,
                                id=app.id,
                                min=min_appver,
                                max=max_appver)