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'))
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'))
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
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'))