def route_checksum_create(component_id): """ Adds a checksum to a component """ # check we have data for key in ['value']: if key not in request.form or not request.form[key]: return _error_internal('No %s specified!' % key) # 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-checksums'): flash('Permission denied: Unable to modify other vendor firmware', 'danger') return redirect( url_for('components.route_show', component_id=component_id)) # validate is a valid hash hash_value = request.form['value'] if _is_sha1(hash_value): hash_kind = 'SHA1' elif _is_sha256(hash_value): hash_kind = 'SHA256' else: flash('%s is not a recognised SHA1 or SHA256 hash' % hash_value, 'warning') return redirect( url_for('components.route_show', component_id=md.component_id, page='checksums')) # check it's not already been added for csum in md.device_checksums: if csum.value == hash_value: flash('%s has already been added' % hash_value, 'warning') return redirect( url_for('components.route_show', component_id=md.component_id, page='checksums')) # add checksum csum = Checksum(kind=hash_kind, value=hash_value) md.device_checksums.append(csum) md.fw.mark_dirty() md.fw.signed_timestamp = None db.session.commit() _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware') flash('Added device checksum', 'info') return redirect( url_for('components.route_show', component_id=md.component_id, page='checksums'))
def route_report(): """ Upload a report """ # only accept form data if request.method != 'POST': return _json_error('only POST supported') # parse both content types, either application/json or multipart/form-data signature = None if request.data: payload = request.data.decode('utf8') elif request.form: data = request.form.to_dict() if 'payload' not in data: return _json_error('No payload in multipart/form-data') payload = data['payload'] if 'signature' in data: signature = data['signature'] else: return _json_error('No data') # find user and verify crt = None if signature: try: info = _pkcs7_signature_info(signature, check_rc=False) except IOError as e: return _json_error('Signature invalid: %s' % str(e)) if 'serial' not in info: return _json_error('Signature invalid, no signature') crt = db.session.query(Certificate).filter( Certificate.serial == info['serial']).first() if crt: try: _pkcs7_signature_verify(crt, payload, signature) except IOError as _: return _json_error('Signature did not validate') # parse JSON data try: item = json.loads(payload) except ValueError as e: return _json_error('No JSON object could be decoded: ' + str(e)) # check we got enough data for key in ['ReportVersion', 'MachineId', 'Reports', 'Metadata']: if not key in item: return _json_error('invalid data, expected %s' % key) if item[key] is None: return _json_error('missing data, expected %s' % key) # parse only this version if item['ReportVersion'] != 2: return _json_error('report version not supported') # add each firmware report machine_id = item['MachineId'] reports = item['Reports'] if len(reports) == 0: return _json_error('no reports included') metadata = item['Metadata'] if len(metadata) == 0: return _json_error('no metadata included') msgs = [] uris = [] for report in reports: for key in ['Checksum', 'UpdateState', 'Metadata']: if not key in report: return _json_error('invalid data, expected %s' % key) if report[key] is None: return _json_error('missing data, expected %s' % key) # flattern the report including the per-machine and per-report metadata data = metadata for key in report: # don't store some data if key in [ 'Created', 'Modified', 'BootTime', 'UpdateState', 'DeviceId', 'UpdateState', 'DeviceId', 'Checksum' ]: continue if key == 'Metadata': md = report[key] for md_key in md: data[md_key] = md[md_key] continue # allow array of strings for any of the keys if isinstance(report[key], list): data[key] = ','.join(report[key]) else: data[key] = report[key] # try to find the checksum (which might not exist on this server) fw = db.session.query(Firmware).filter( Firmware.checksum_signed_sha1 == report['Checksum']).first() if not fw: fw = db.session.query(Firmware).filter( Firmware.checksum_signed_sha256 == report['Checksum']).first() if not fw: msgs.append('%s did not match any known firmware archive' % report['Checksum']) continue # cannot report this failure if fw.do_not_track: msgs.append('%s will not accept reports' % report['Checksum']) continue # update the device checksums if there is only one component if crt and crt.user.check_acl( '@qa') and 'ChecksumDevice' in data and len(fw.mds) == 1: md = fw.md_prio found = False # fwupd v1.2.6 sends an array of strings, before that just a string checksums = data['ChecksumDevice'] if not isinstance(checksums, list): checksums = [checksums] # does the submitted checksum already exist as a device checksum for checksum in checksums: for csum in md.device_checksums: if csum.value == checksum: found = True break if found: continue _event_log('added device checksum %s to firmware %s' % (checksum, md.fw.checksum_upload_sha1)) if _is_sha1(checksum): md.device_checksums.append(Checksum(checksum, 'SHA1')) elif _is_sha256(checksum): md.device_checksums.append(Checksum(checksum, 'SHA256')) # find any matching report issue_id = 0 if report['UpdateState'] == 3: issue = _find_issue_for_report_data(data, fw) if issue: issue_id = issue.issue_id msgs.append('The failure is a known issue') uris.append(issue.url) # update any old report r = db.session.query(Report).\ filter(Report.checksum == report['Checksum']).\ filter(Report.machine_id == machine_id).first() if r: msgs.append('%s replaces old report' % report['Checksum']) r.state = report['UpdateState'] for e in r.attributes: db.session.delete(e) else: # save a new report in the database r = Report(machine_id=machine_id, firmware_id=fw.firmware_id, issue_id=issue_id, state=report['UpdateState'], checksum=report['Checksum']) # update the firmware so that the QA user does not have to wait 24h if r.state == 2: fw.report_success_cnt += 1 elif r.state == 3: if r.issue_id: fw.report_issue_cnt += 1 else: fw.report_failure_cnt += 1 # update the LVFS user if crt: r.user_id = crt.user_id # save all the report entries for key in data: r.attributes.append(ReportAttribute(key=key, value=data[key])) db.session.add(r) # all done db.session.commit() # put messages and URIs on one line return _json_success(msg='; '.join(msgs) if msgs else None, uri='; '.join(uris) if uris else None)
def _parse_release(md, release): # get description try: md.release_description = _node_validate_text( release.xpath('description')[0], minlen=3, maxlen=1000, nourl=True) except IndexError as _: pass md.install_duration = int(release.get('install_duration', '0')) md.release_urgency = release.get('urgency') # date, falling back to timestamp if 'date' in release.attrib: try: dt = datetime.datetime.strptime(release.get('date'), "%Y-%m-%d") dt_utc = dt.replace(tzinfo=datetime.timezone.utc) md.release_timestamp = int(dt_utc.timestamp()) except ValueError as e: raise MetadataInvalid( '<release> has invalid date attribute: {}'.format(str(e))) elif 'timestamp' in release.attrib: try: md.release_timestamp = int(release.get('timestamp')) except ValueError as e: raise MetadataInvalid( '<release> has invalid timestamp attribute: {}'.format( str(e))) else: raise MetadataInvalid( '<release> had no date or timestamp attributes') # optional release tag if 'tag' in release.attrib: md.release_tag = release.attrib['tag'] if len(md.release_tag) < 4: raise MetadataInvalid( '<release> tag was too short to identify the firmware') md.add_keywords_from_string(md.release_tag, priority=5) # get list of CVEs for issue in release.xpath('issues/issue'): kind = issue.get('type') if not kind: raise MetadataInvalid('<issue> had no type attribute') if kind != 'cve': raise MetadataInvalid('<issue> type can only be \'cve\'') value = _node_validate_text(issue, minlen=3, maxlen=1000, nourl=True) md.issues.append(ComponentIssue(kind=kind, value=value)) # get <url type="details"> try: md.details_url = _node_validate_text( release.xpath('url[@type="details"]')[0], minlen=12, maxlen=1000) except IndexError as _: pass # get <url type="source"> try: md.source_url = _node_validate_text( release.xpath('url[@type="source"]')[0], minlen=12, maxlen=1000) except IndexError as _: pass # fix up hex version md.version = release.get('version') if not md.version: raise MetadataInvalid('<release> had no version attribute') if md.version.startswith('0x'): md.version = str(int(md.version[2:], 16)) # ensure there's always a contents filename try: md.filename_contents = release.xpath( 'checksum[@target="content"]')[0].get('filename') except IndexError as _: pass if not md.filename_contents: md.filename_contents = 'firmware.bin' # ensure there's always a contents filename for csum in release.xpath('checksum[@target="device"]'): text = _node_validate_text(csum, minlen=32, maxlen=128) if csum.get('kind') == 'sha1': md.device_checksums.append(Checksum(text, 'SHA1')) elif csum.get('kind') == 'sha256': md.device_checksums.append(Checksum(text, 'SHA256')) if not md.filename_contents: md.filename_contents = 'firmware.bin'