Exemplo n.º 1
0
def route_requirement_create(component_id):
    """ Adds a requirement to a component """

    # check we have data
    for key in ['kind', 'value']:
        if key not in request.form or not request.form[key]:
            return _error_internal('No %s specified!' % key)
    if request.form['kind'] not in ['hardware', 'firmware', 'id']:
        return _error_internal('No valid kind specified!')

    # get firmware component
    md = db.session.query(Component).\
            filter(Component.component_id == component_id).first()
    if not md:
        flash('No component matched!', 'danger')
        return redirect(url_for('firmware.route_firmware'))

    # security check
    if not md.check_acl('@modify-requirements'):
        flash('Permission denied: Unable to modify other vendor firmware',
              'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # validate CHID is a valid GUID
    if request.form['kind'] == 'hardware' and not _validate_guid(
            request.form['value']):
        flash(
            'Cannot add requirement: %s is not a valid GUID' %
            request.form['value'], 'warning')
        return redirect(
            url_for('components.route_show',
                    component_id=md.component_id,
                    page='requires'))

    # support empty too
    compare = request.form.get('compare', None)
    if not compare:
        compare = None
    version = request.form.get('version', None)
    if not version:
        version = None
    depth = request.form.get('depth', None)
    if not depth:
        depth = None

    # add requirement
    rq = Requirement(kind=request.form['kind'],
                     value=request.form['value'].strip(),
                     compare=compare,
                     version=version,
                     depth=depth)
    md.requirements.append(rq)
    md.fw.mark_dirty()
    db.session.commit()
    flash('Added requirement', 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='requires'))
Exemplo n.º 2
0
 def test_validate_guid(self):
     self.assertTrue(_validate_guid('84f40464-9272-4ef7-9399-cd95f12da696'))
     self.assertFalse(_validate_guid(None))
     self.assertFalse(_validate_guid(''))
     self.assertFalse(_validate_guid('hello dave'))
     self.assertFalse(_validate_guid('84F40464-9272-4EF7-9399-CD95F12DA696'))
     self.assertFalse(_validate_guid('84f40464-9272-4ef7-9399'))
     self.assertFalse(_validate_guid('84f40464-9272-4ef7-xxxx-cd95f12da696'))
Exemplo n.º 3
0
    def _parse_component(self, component):

        # get priority
        md = Component()
        md.priority = int(component.get('priority', '0'))

        # check type
        if component.get('type') != 'firmware':
            raise MetadataInvalid('<component type="firmware"> required')

        # get <id>
        try:
            md.appstream_id = _node_validate_text(component.xpath('id')[0],
                                                  minlen=10,
                                                  maxlen=256)
            if not md.appstream_id:
                raise MetadataInvalid('<id> value invalid')
            for char in md.appstream_id:
                if char.isspace():
                    raise MetadataInvalid('<id> Cannot contain spaces')
                if char in ['/', '\\']:
                    raise MetadataInvalid('<id> Cannot contain slashes')
                if char not in ['-', '_', '.'] and not char.isalnum():
                    raise MetadataInvalid(
                        '<id> Cannot contain {}'.format(char))
            if len(md.appstream_id.split('.')) < 4:
                raise MetadataInvalid(
                    '<id> Should contain at least 4 sections to identify the model'
                )
        except IndexError as _:
            raise MetadataInvalid('<id> tag missing')

        # get <developer_name>
        try:
            md.developer_name = _node_validate_text(
                component.xpath('developer_name')[0],
                minlen=3,
                maxlen=50,
                nourl=True)
            if md.developer_name == 'LenovoLtd.':
                md.developer_name = 'Lenovo Ltd.'
            md.add_keywords_from_string(md.developer_name, priority=10)
        except IndexError as _:
            raise MetadataInvalid('<developer_name> tag missing')
        if md.developer_name.find('@') != -1 or md.developer_name.find(
                '_at_') != -1:
            raise MetadataInvalid(
                '<developer_name> cannot contain an email address')

        # get <name>
        try:
            md.name = _node_validate_text(component.xpath('name')[0],
                                          minlen=3,
                                          maxlen=500)
            md.add_keywords_from_string(md.name, priority=3)

            # use categories instead
            if self.is_strict:
                category = {
                    'system': 'X-System',
                    'device': 'X-Device',
                    'bios': 'X-System',
                    'me': 'X-ManagementEngine',
                    'embedded': 'X-EmbeddedController',
                    'controller': 'X-EmbeddedController',
                }
                words = [word.lower() for word in md.name.split(' ')]
                for search in category:
                    if search in words:
                        raise MetadataInvalid('<name> tag should not contain {}, use '
                                              '<categories><category>{}'
                                              '</category></categories> instead'.\
                                              format(search, category[search]))

                # tokens banned outright
                for search in ['firmware', 'update', '(r)', '(c)']:
                    if search in words:
                        raise MetadataInvalid('<name> tag should not contain '
                                              'the word "{}"'.format(search))

                # should not include the vendor in the name
                if md.developer_name_display:
                    if md.developer_name_display.lower() in words:
                        raise MetadataInvalid('<name> tag should not contain '
                                              'the vendor name "{}"'.format(
                                                  md.developer_name_display))
        except IndexError as _:
            raise MetadataInvalid('<name> tag missing')

        # get <summary>
        try:
            md.summary = _node_validate_text(component.xpath('summary')[0],
                                             minlen=10,
                                             maxlen=500)
            md.add_keywords_from_string(md.summary, priority=1)
        except IndexError as _:
            raise MetadataInvalid('<summary> tag missing')

        # get optional <name_variant_suffix>
        try:
            md.name_variant_suffix = _node_validate_text(
                component.xpath('name_variant_suffix')[0],
                minlen=2,
                maxlen=500)
        except IndexError as _:
            pass

        # get optional <description}
        try:
            md.description = _node_validate_text(
                component.xpath('description')[0],
                minlen=25,
                maxlen=1000,
                nourl=True)
        except IndexError as _:
            pass

        # get <metadata_license>
        if self.is_strict:
            try:
                md.metadata_license = _node_validate_text(
                    component.xpath('metadata_license')[0])
                if md.metadata_license not in [
                        'CC0-1.0', 'FSFAP', 'CC-BY-3.0', 'CC-BY-SA-3.0',
                        'CC-BY-4.0', 'CC-BY-SA-4.0', 'GFDL-1.1', 'GFDL-1.2',
                        'GFDL-1.3'
                ]:
                    raise MetadataInvalid(
                        'Invalid <metadata_license> tag of {}'.format(
                            md.metadata_license))
            except AttributeError as _:
                raise MetadataInvalid('<metadata_license> tag')
            except IndexError as _:
                raise MetadataInvalid('<metadata_license> tag missing')

        # get <project_license>
        try:
            md.project_license = _node_validate_text(
                component.xpath('project_license')[0],
                minlen=4,
                maxlen=50,
                nourl=True)
        except IndexError as _:
            raise MetadataInvalid('<project_license> tag missing')
        if not md.project_license:
            raise MetadataInvalid('<project_license> value invalid')

        # get <url type="homepage">
        try:
            md.url_homepage = _node_validate_text(
                component.xpath('url[@type="homepage"]')[0],
                minlen=7,
                maxlen=1000)
        except IndexError as _:
            raise MetadataInvalid('<url type="homepage"> tag missing')
        if not md.url_homepage:
            raise MetadataInvalid('<url type="homepage"> value invalid')

        # add manually added keywords
        for keyword in component.xpath('keywords/keyword'):
            text = _node_validate_text(keyword,
                                       minlen=3,
                                       maxlen=50,
                                       nourl=True)
            if text.find(' ') != -1:
                raise MetadataInvalid('<keywords> cannot contain spaces')
            md.add_keywords_from_string(text, priority=5)

        # add provides
        for prov in component.xpath('provides/firmware[@type="flashed"]'):
            text = _node_validate_text(prov, minlen=5, maxlen=1000)
            if not _validate_guid(text):
                raise MetadataInvalid('The GUID {} was invalid.'.format(text))
            if text in [
                    '230c8b18-8d9b-53ec-838b-6cfc0383493a',  # main-system-firmware
                    'f15aa55c-9cd5-5942-85ae-a6bf8740b96c',  # MST-panamera
                    'd6072785-6fc0-5f83-9d49-11376e7f48b1',  # MST-leaf
                    '49ec4eb4-c02b-58fc-8935-b1ee182405c7'
            ]:  # MST-tesla
                raise MetadataInvalid(
                    'The GUID {} is too generic'.format(text))
            md.guids.append(Guid(md.component_id, text))
        if not md.guids:
            raise MetadataInvalid(
                'The metadata file did not provide any GUID.')

        # check the file didn't try to add it's own <require> on vendor-id
        # to work around the vendor-id security checks in fwupd
        if component.xpath('requires/firmware[text()="vendor-id"]'):
            raise MetadataInvalid('Firmware cannot specify vendor-id')

        # check only recognised requirements are added
        for req in component.xpath('requires/*'):
            if req.tag == 'firmware':
                text = _node_validate_text(req,
                                           minlen=3,
                                           maxlen=1000,
                                           allow_none=True)
                rq = Requirement(kind=req.tag,
                                 value=text,
                                 compare=req.get('compare'),
                                 version=req.get('version'),
                                 depth=req.get('depth', None))
                md.requirements.append(rq)
            elif req.tag == 'id':
                text = _node_validate_text(req, minlen=3, maxlen=1000)
                rq = Requirement(kind=req.tag,
                                 value=text,
                                 compare=req.get('compare'),
                                 version=req.get('version'))
                md.requirements.append(rq)
                if text == 'org.freedesktop.fwupd':
                    self.fwupd_min_version = req.get('version')
            elif req.tag == 'hardware':
                text = _node_validate_text(req, minlen=3, maxlen=1000)
                for req_value in text.split('|'):
                    rq = Requirement(kind=req.tag,
                                     value=req_value,
                                     compare=req.get('compare'),
                                     version=req.get('version'))
                    md.requirements.append(rq)
            else:
                raise MetadataInvalid('<{}> requirement was invalid'.format(
                    req.tag))

        # from the first screenshot
        try:
            md.screenshot_caption = _node_validate_text(
                component.xpath('screenshots/screenshot/caption')[0],
                minlen=8,
                maxlen=1000,
                nourl=True)
        except IndexError as _:
            pass
        try:
            md.screenshot_url = _node_validate_text(
                component.xpath('screenshots/screenshot/image')[0],
                minlen=8,
                maxlen=1000)
        except IndexError as _:
            pass

        # allows OEM to hide the direct download link on the LVFS
        if component.xpath('custom/value[@key="LVFS::InhibitDownload"]'):
            md.inhibit_download = True

        # allows OEM to disable ignore all kinds of statistics on this firmware
        if component.xpath('custom/value[@key="LVFS::DoNotTrack"]'):
            md.fw.do_not_track = True

        # allows OEM to change the triplet (AA.BB.CCDD) to quad (AA.BB.CC.DD)
        try:
            version_format = _node_validate_text(
                component.xpath('custom/value[@key="LVFS::VersionFormat"]')[0])
            if not self.version_formats:
                raise MetadataInvalid(
                    'Valid version formats have not been added')
            if version_format not in self.version_formats:
                raise MetadataInvalid('LVFS::VersionFormat can only be {}'.\
                                      format(','.join(self.version_formats.keys())))
            md.verfmt = self.version_formats[version_format]
        except IndexError as _:
            pass

        # enforce the VersionFormat if the version is an integer
        if self.is_strict and md.version:
            if md.version.isdigit() and not md.version_format:
                raise MetadataInvalid(
                    'LVFS::VersionFormat is required for integer version')

        # allows OEM to specify protocol
        try:
            text = _node_validate_text(
                component.xpath('custom/value[@key="LVFS::UpdateProtocol"]')
                [0])
            if text not in self.protocol_map:
                raise MetadataInvalid(
                    'No valid UpdateProtocol {} found'.format(text))
            md.protocol_id = self.protocol_map[text]
        except IndexError as _:
            pass

        # allows OEM to set banned country codes
        try:
            text = _node_validate_text(component.xpath(
                'custom/value[@key="LVFS::BannedCountryCodes"]')[0],
                                       minlen=2,
                                       maxlen=1000,
                                       nourl=True)
            self.fw.banned_country_codes = text
        except IndexError as _:
            pass

        # should we parse the .inf file?
        try:
            text = _node_validate_text(component.xpath(
                'custom/value[@key="LVFS::EnableInfParsing"]')[0],
                                       minlen=2,
                                       maxlen=10,
                                       nourl=True)
            if text == 'true':
                self.enable_inf_parsing = True
            elif text == 'false':
                self.enable_inf_parsing = False
            else:
                raise MetadataInvalid(
                    'LVFS::EnableInfParsing only allowed true or false, got {}'
                    .format(text))
        except IndexError as _:
            pass

        # allows OEM to specify category
        for category in component.xpath('categories/category'):
            text = _node_validate_text(category,
                                       minlen=8,
                                       maxlen=50,
                                       nourl=True)
            if text in self.category_map:
                md.category_id = self.category_map[text]
                break

        # parse the default (first) release
        try:
            default_release = component.xpath('releases/release')[0]
        except IndexError as _:
            raise MetadataInvalid(
                'The metadata file did not provide any releases')
        self._parse_release(md, default_release)

        # ensure the update description does not refer to a file in the archive
        if md.release_description:
            for word in md.release_description.split(' '):
                if word.find('.') == -1:  # any word without a dot is not a fn
                    continue
                if word in self.cabarchive_upload:
                    raise MetadataInvalid(
                        'The release description should not reference other files.'
                    )

        # check the inf file matches up with the .xml file
        if self._version_inf and self._version_inf != md.version:
            raise MetadataInvalid(
                'The inf Firmware_AddReg[HKR->FirmwareVersion] '
                '%s did not match the metainfo.xml value %s.' %
                (self._version_inf, md.version))

        # success
        return md
Exemplo n.º 4
0
def route_requirement_modify(component_id):
    """ Adds a requirement to a component """

    # check we have data
    for key in ['kind', 'value']:
        if key not in request.form:
            return _error_internal('No %s specified!' % key)
    if request.form['kind'] not in ['hardware', 'firmware', 'id']:
        return _error_internal('No valid kind specified!')

    # get firmware component
    md = db.session.query(Component).\
            filter(Component.component_id == component_id).first()
    if not md:
        flash('No component matched!', 'danger')
        return redirect(url_for('firmware.route_firmware'))

    # security check
    if not md.check_acl('@modify-requirements'):
        flash('Permission denied: Unable to modify other vendor firmware',
              'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # validate CHID is a valid GUID
    if request.form['kind'] == 'hardware' and not _validate_guid(
            request.form['value']):
        flash(
            'Cannot add requirement: %s is not a valid GUID' %
            request.form['value'], 'warning')
        return redirect(
            url_for('components.route_show',
                    component_id=md.component_id,
                    page='requires'))

    # empty string is None
    value = request.form['value']
    if not value:
        value = None

    # check it's not already been added
    rq = md.find_req(request.form['kind'], value)
    if rq:
        if 'version' in request.form:
            rq.version = request.form['version']
        if 'compare' in request.form:
            if request.form['compare'] == 'any':
                db.session.delete(rq)
                db.session.commit()
                flash('Deleted requirement %s' % rq.value, 'info')
                return redirect(
                    url_for('components.route_show',
                            component_id=md.component_id,
                            page='requires'))
            rq.compare = request.form['compare']
        db.session.commit()
        if rq.value:
            flash('Modified requirement %s' % rq.value, 'info')
        else:
            flash('Modified requirement firmware', 'info')
        return redirect(
            url_for('components.route_show',
                    component_id=md.component_id,
                    page='requires'))

    # add requirement
    rq = Requirement(
        kind=request.form['kind'],
        value=value,
        compare=request.form.get('compare', None),
        version=request.form.get('version', None),
        depth=request.form.get('depth', None),
    )
    md.requirements.append(rq)
    md.fw.mark_dirty()
    db.session.commit()
    flash('Added requirement', 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='requires'))