示例#1
0
def route_issue_delete(component_id, component_issue_id):

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

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

    # remove issue
    db.session.delete(issue)
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    # log
    flash('Removed {}'.format(issue.value), 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='issues'))
示例#2
0
def route_keyword_create(component_id):
    """ Adds one or more keywords to the existing 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-keywords'):
        flash('Permission denied: Unable to modify other vendor firmware',
              'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # add keyword
    md.add_keywords_from_string(request.form['value'])
    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 keywords', 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='keywords'))
示例#3
0
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'))
示例#4
0
def route_issue_create(component_id):
    """ Adds one or more CVEs to the existing 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-updateinfo'):
        flash('Permission denied: Unable to modify firmware', 'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # add issue
    for value in request.form['value'].split(','):
        if value in md.issue_values:
            flash('Already exists: {}'.format(value), 'info')
            continue
        if value.startswith('CVE-'):
            issue = ComponentIssue(kind='cve', value=value)
        elif value.startswith('DSA-'):
            issue = ComponentIssue(kind='dell', value=value)
        elif value.startswith('LEN-'):
            issue = ComponentIssue(kind='lenovo', value=value)
        elif value.startswith('INTEL-SA-'):
            issue = ComponentIssue(kind='intel', value=value)
        else:
            flash('Issue invalid: {}'.format(value), 'danger')
            return redirect(
                url_for('components.route_show',
                        component_id=component_id,
                        page='issues'))
        if issue.problem:
            flash('Issue invalid: {}'.format(issue.problem.description),
                  'danger')
            return redirect(
                url_for('components.route_show',
                        component_id=component_id,
                        page='issues'))
        flash('Added {}'.format(value), 'info')
        md.issues.append(issue)
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='issues'))
示例#5
0
def route_checksum_delete(component_id, checksum_id):

    # get firmware component
    csum = db.session.query(Checksum).filter(
        Checksum.checksum_id == checksum_id).first()
    if not csum:
        flash('No checksum matched!', 'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # get the component for the checksum
    md = csum.md
    if md.component_id != component_id:
        flash('Wrong component ID for checksum', 'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))
    if not md:
        return _error_internal('No metadata matched!')

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

    # remove chid
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.delete(csum)
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    # log
    flash('Removed device checksum', 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='checksums'))
示例#6
0
def route_issue_autoimport(component_id):

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

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

    # find any valid CVE numbers in the existing description
    n_issues = _autoimport_issues(md, 'CVE-', 'cve')
    n_issues += _autoimport_issues(md, 'DSA-', 'dell')
    n_issues += _autoimport_issues(md, 'LEN-', 'lenovo')
    n_issues += _autoimport_issues(md, 'INTEL-SA-', 'intel')
    n_issues += _autoimport_issues(md, 'INTEL-TA-', 'intel')

    # success
    if not n_issues:
        flash('No issues could be detected', 'info')
    else:
        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 {} issues — now review the update description for sanity'.
            format(n_issues), 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='update'))
示例#7
0
def route_keyword_delete(component_id, keyword_id):

    # get firmware component
    kw = db.session.query(Keyword).filter(
        Keyword.keyword_id == keyword_id).first()
    if not kw:
        flash('No keyword matched!', 'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # get the firmware for the keyword
    md = kw.md
    if md.component_id != component_id:
        return _error_internal('Wrong component ID for keyword!')
    if not md:
        return _error_internal('No metadata matched!')

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

    # remove chid
    db.session.delete(kw)
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    # log
    flash('Removed keyword %s' % kw.value, 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='keywords'))
示例#8
0
def route_requirement_delete(component_id, requirement_id):

    # get firmware component
    rq = db.session.query(Requirement).filter(
        Requirement.requirement_id == requirement_id).first()
    if not rq:
        flash('No requirement matched!', 'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # get the firmware for the requirement
    md = rq.md
    if md.component_id != component_id:
        return _error_internal('Wrong component ID for requirement!')
    if not md:
        return _error_internal('No metadata matched!')

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

    # remove chid
    db.session.delete(rq)
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    # log
    flash('Removed requirement %s' % rq.value, 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='requires'))
示例#9
0
def _upload_firmware():

    # verify the user can upload
    if not _user_can_upload(g.user):
        flash('User has not signed legal agreement', 'danger')
        return redirect(url_for('main.route_dashboard'))

    # used a custom vendor_id
    if 'vendor_id' in request.form:
        try:
            vendor_id = int(request.form['vendor_id'])
        except ValueError as e:
            flash(
                'Failed to upload file: Specified vendor ID %s invalid' %
                request.form['vendor_id'], 'warning')
            return redirect(url_for('upload.route_firmware'))
        vendor = db.session.query(Vendor).filter(
            Vendor.vendor_id == vendor_id).first()
        if not vendor:
            flash('Failed to upload file: Specified vendor ID not found',
                  'warning')
            return redirect(url_for('upload.route_firmware'))
    else:
        vendor = g.user.vendor

    # security check
    if not vendor.check_acl('@upload'):
        flash(
            'Permission denied: Failed to upload file for vendor: '
            'User with vendor %s cannot upload to vendor %s' %
            (g.user.vendor.group_id, vendor.group_id), 'warning')
        return redirect(url_for('upload.route_firmware'))

    # not correct parameters
    if not 'target' in request.form:
        return _error_internal('No target')
    if not 'file' in request.files:
        return _error_internal('No file')
    if request.form['target'] not in ['private', 'embargo', 'testing']:
        return _error_internal('Target not valid')

    # find remote, creating if required
    remote_name = request.form['target']
    if remote_name == 'embargo':
        remote = vendor.remote
    else:
        remote = db.session.query(Remote).filter(
            Remote.name == remote_name).first()
    if not remote:
        return _error_internal('No remote for target %s' % remote_name)

    # if the vendor has uploaded a lot of firmware don't start changing the rules
    is_strict = len(vendor.fws) < 500

    # load in the archive
    fileitem = request.files['file']
    if not fileitem:
        return _error_internal('No file object')
    try:
        ufile = UploadedFile(is_strict=is_strict)
        for cat in db.session.query(Category):
            ufile.category_map[cat.value] = cat.category_id
        for pro in db.session.query(Protocol):
            ufile.protocol_map[pro.value] = pro.protocol_id
        for verfmt in db.session.query(Verfmt):
            ufile.version_formats[verfmt.value] = verfmt
        ufile.parse(os.path.basename(fileitem.filename), fileitem.read())
    except (FileTooLarge, FileTooSmall, FileNotSupported,
            MetadataInvalid) as e:
        flash('Failed to upload file: ' + str(e), 'danger')
        return redirect(request.url)

    # check the file does not already exist
    fw = db.session.query(Firmware)\
                   .filter(or_(Firmware.checksum_upload_sha1 == ufile.fw.checksum_upload_sha1,
                               Firmware.checksum_upload_sha256 == ufile.fw.checksum_upload_sha256)).first()
    if fw:
        if fw.check_acl('@view'):
            flash(
                'Failed to upload file: A file with hash %s already exists' %
                fw.checksum_upload_sha1, 'warning')
            return redirect('/lvfs/firmware/%s' % fw.firmware_id)
        flash(
            'Failed to upload file: Another user has already uploaded this firmware',
            'warning')
        return redirect(url_for('upload.route_firmware'))

    # check the guid and version does not already exist
    fws = db.session.query(Firmware).all()
    fws_already_exist = []
    for md in ufile.fw.mds:
        provides_value = md.guids[0].value
        fw = _filter_fw_by_id_guid_version(fws, md.appstream_id,
                                           provides_value, md.version)
        if fw:
            fws_already_exist.append(fw)

    # all the components existed, so build an error out of all the versions
    if len(fws_already_exist) == len(ufile.fw.mds):
        if g.user.check_acl('@robot') and 'auto-delete' in request.form:
            for fw in fws_already_exist:
                if fw.remote.is_public:
                    flash(
                        'Firmware {} cannot be autodeleted as is in remote {}'.
                        format(fw.firmware_id, fw.remote.name), 'danger')
                    return redirect(url_for('upload.route_firmware'))
                if fw.user.user_id != g.user.user_id:
                    flash('Firmware was not uploaded by this user', 'danger')
                    return redirect(url_for('upload.route_firmware'))
            for fw in fws_already_exist:
                flash('Firmware %i was auto-deleted due to robot upload' %
                      fw.firmware_id)
                _firmware_delete(fw)
        else:
            versions_for_display = []
            for fw in fws_already_exist:
                for md in fw.mds:
                    if not md.version_display in versions_for_display:
                        versions_for_display.append(md.version_display)
            flash(
                'Failed to upload file: A firmware file for this device with '
                'version %s already exists' % ','.join(versions_for_display),
                'danger')
            return redirect('/lvfs/firmware/%s' % fw.firmware_id)

    # check if the file dropped a GUID previously supported
    for umd in ufile.fw.mds:
        new_guids = [guid.value for guid in umd.guids]
        for md in db.session.query(Component).\
                        filter(Component.appstream_id == umd.appstream_id):
            if md.fw.is_deleted:
                continue
            for old_guid in [guid.value for guid in md.guids]:
                if old_guid in new_guids:
                    continue
                fw_str = str(md.fw.firmware_id)
                if g.user.check_acl('@qa') or g.user.check_acl('@robot'):
                    flash(
                        'Firmware drops GUID {} previously supported '
                        'in firmware {}'.format(old_guid, fw_str), 'warning')
                else:
                    flash(
                        'Firmware would drop GUID {} previously supported '
                        'in firmware {}'.format(old_guid, fw_str), 'danger')
                    return redirect(request.url)

    # allow plugins to copy any extra files from the source archive
    for cffile in ufile.cabarchive_upload.values():
        ploader.archive_copy(ufile.cabarchive_repacked, cffile)

    # allow plugins to add files
    ploader.archive_finalize(ufile.cabarchive_repacked,
                             _get_plugin_metadata_for_uploaded_file(ufile))

    # dump to a file
    download_dir = app.config['DOWNLOAD_DIR']
    if not os.path.exists(download_dir):
        os.mkdir(download_dir)
    fn = os.path.join(download_dir, ufile.fw.filename)
    cab_data = ufile.cabarchive_repacked.save(compress=True)
    with open(fn, 'wb') as f:
        f.write(cab_data)

    # create parent firmware object
    settings = _get_settings()
    target = request.form['target']
    fw = ufile.fw
    fw.vendor = vendor
    fw.vendor_odm = g.user.vendor
    fw.user = g.user
    fw.addr = _get_client_address()
    fw.remote = remote
    fw.checksum_signed_sha1 = hashlib.sha1(cab_data).hexdigest()
    fw.checksum_signed_sha256 = hashlib.sha256(cab_data).hexdigest()
    fw.is_dirty = True
    fw.failure_minimum = settings['default_failure_minimum']
    fw.failure_percentage = settings['default_failure_percentage']

    # fix name
    for md in fw.mds:
        name_fixed = _fix_component_name(md.name, md.developer_name_display)
        if name_fixed != md.name:
            flash(
                'Fixed component name from "%s" to "%s"' %
                (md.name, name_fixed), 'warning')
            md.name = name_fixed

    # verify each component has a version format
    for md in fw.mds:
        if not md.verfmt_with_fallback:
            flash('Component {} does not have required LVFS::VersionFormat'.\
                  format(md.appstream_id), 'warning')

    # add to database
    fw.events.append(
        FirmwareEvent(remote_id=remote.remote_id, user_id=g.user.user_id))
    db.session.add(fw)
    db.session.commit()

    # ensure the test has been added for the firmware type
    ploader.ensure_test_for_fw(fw)

    # sync everything we added
    db.session.commit()

    # asynchronously run
    _async_test_run_for_firmware.apply_async(args=(fw.firmware_id, ))

    # send out emails to anyone interested
    for u in fw.get_possible_users_to_email:
        if u == g.user:
            continue
        if u.get_action('notify-upload-vendor') and u.vendor == fw.vendor:
            send_email(
                "[LVFS] Firmware has been uploaded", u.email_address,
                render_template('email-firmware-uploaded.txt',
                                user=u,
                                user_upload=g.user,
                                fw=fw))
        elif u.get_action('notify-upload-affiliate'):
            send_email(
                "[LVFS] Firmware has been uploaded by affiliate",
                u.email_address,
                render_template('email-firmware-uploaded.txt',
                                user=u,
                                user_upload=g.user,
                                fw=fw))

    flash('Uploaded file %s to %s' % (ufile.fw.filename, target), 'info')

    # asynchronously sign
    _async_sign_fw.apply_async(args=(fw.firmware_id, ), queue='firmware')

    # invalidate
    if target == 'embargo':
        remote.is_dirty = True
        g.user.vendor.remote.is_dirty = True
        db.session.commit()

    return redirect(url_for('firmware.route_show', firmware_id=fw.firmware_id))
示例#10
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')
    else:
        # 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)
        flash('Added requirement', 'info')

    # asynchronously sign
    md.fw.mark_dirty()
    md.fw.signed_timestamp = None
    db.session.commit()
    _async_sign_fw.apply_async(args=(md.fw.firmware_id, ), queue='firmware')

    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='requires'))
示例#11
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

    # firmware is unset value
    value = request.form['value'].strip()
    if value == 'self':
        value = None

    # add requirement
    rq = Requirement(kind=request.form['kind'],
                     value=value,
                     compare=compare,
                     version=version,
                     depth=depth)
    md.requirements.append(rq)
    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 requirement', 'info')
    return redirect(
        url_for('components.route_show',
                component_id=md.component_id,
                page='requires'))
示例#12
0
def route_modify(component_id):
    """ Modifies the component properties """

    # find firmware
    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-updateinfo'):
        flash('Permission denied: Insufficient permissions to modify firmware',
              'danger')
        return redirect(
            url_for('components.route_show', component_id=component_id))

    # set new metadata values
    page = 'overview'
    retry_all_tests = False
    if 'screenshot_url' in request.form:
        md.screenshot_url = request.form['screenshot_url']
    if 'protocol_id' in request.form:
        if md.protocol_id != request.form['protocol_id']:
            md.protocol_id = request.form['protocol_id']
            retry_all_tests = True
    if 'category_id' in request.form:
        category_id = request.form['category_id']
        if not category_id:
            category_id = None
        if md.category_id != category_id:
            md.category_id = category_id
            retry_all_tests = True
    if 'screenshot_caption' in request.form:
        md.screenshot_caption = _sanitize_markdown_text(
            request.form['screenshot_caption'])
    if 'install_duration' in request.form:
        try:
            md.install_duration = int(request.form['install_duration'])
        except ValueError as _:
            md.install_duration = 0
        page = 'install_duration'
    if 'urgency' in request.form:
        md.release_urgency = request.form['urgency']
        page = 'update'
    if 'description' in request.form:
        md.release_description = _sanitize_markdown_text(
            request.form['description'])
        page = 'update'
    if 'details_url' in request.form:
        md.details_url = request.form['details_url']
        page = 'update'
    if 'source_url' in request.form:
        md.source_url = request.form['source_url']
        page = 'update'
    if 'appstream_id' in request.form:
        md.appstream_id = request.form['appstream_id']
    if 'name' in request.form:
        md.name = request.form['name']
    if 'name_variant_suffix' in request.form:
        md.name_variant_suffix = request.form['name_variant_suffix']
    if 'release_tag' in request.form:
        md.release_tag = request.form['release_tag']
    if 'release_message' in request.form:
        md.release_message = request.form['release_message']

    # the firmware changed protocol
    if retry_all_tests:
        for test in md.fw.tests:
            test.retry()

    # ensure the test has been added for the new firmware type
    ploader.ensure_test_for_fw(md.fw)

    # sync everything we added
    db.session.commit()

    # asynchronously run
    _async_test_run_for_firmware.apply_async(args=(md.fw.firmware_id, ))

    # modify
    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('Component updated', 'info')
    return redirect(
        url_for('components.route_show', component_id=component_id, page=page))