Exemple #1
0
def _main_with_app_context():
    if 'repair-ts' in sys.argv:
        _repair_ts()
    if 'repair-csum' in sys.argv:
        _repair_csum()
    if 'fsck' in sys.argv:
        _fsck()
    if 'ensure' in sys.argv:
        _ensure_tests()
    if 'firmware' in sys.argv:
        _regenerate_and_sign_firmware()
    if 'metadata' in sys.argv:
        _regenerate_and_sign_metadata()
    if 'metadata-embargo' in sys.argv:
        _regenerate_and_sign_metadata(only_embargo=True)
    if 'purgedelete' in sys.argv:
        _delete_embargo_obsoleted_fw()
        _purge_old_deleted_firmware()
    if 'fwchecks' in sys.argv:
        _check_firmware()
        _yara_query_all()
        _user_disable_notify()
        _user_disable_actual()
    if 'stats' in sys.argv:
        val = _get_datestr_from_datetime(datetime.date.today() -
                                         datetime.timedelta(days=1))
        _generate_stats_for_datestr(val)
        _generate_stats()
    if 'statsmigrate' in sys.argv:
        for days in range(1, 720):
            val = _get_datestr_from_datetime(datetime.date.today() -
                                             datetime.timedelta(days=days))
            _generate_stats_for_datestr(val)
def route_year(ts=3):
    """ A analytics screen to show information about users """

    # this is somewhat klunky
    data = []
    now = datetime.date.today() - datetime.timedelta(days=1)
    for _ in range(12 * ts):
        datestrold = _get_datestr_from_datetime(now)
        now -= datetime.timedelta(days=30)
        datestrnew = _get_datestr_from_datetime(now)
        analytics = db.session.query(Analytic).\
                        filter(Analytic.datestr < datestrold).\
                        filter(Analytic.datestr > datestrnew).\
                        all()

        # sum up all the totals for each day in that month
        cnt = 0
        for analytic in analytics:
            cnt += analytic.cnt
        data.append(int(cnt))

    return render_template('analytics-year.html',
                           category='analytics',
                           labels_months=_get_chart_labels_months(ts)[::-1],
                           data_months=data[::-1])
Exemple #3
0
def route_vendor(timespan_days=30):
    """ A analytics screen to show information about users """

    # get data for this time period
    cnt_total = {}
    cached_cnt = {}
    yesterday = datetime.date.today() - datetime.timedelta(days=1)
    datestr_start = _get_datestr_from_datetime(yesterday - datetime.timedelta(days=timespan_days))
    datestr_end = _get_datestr_from_datetime(yesterday)
    for ug in db.session.query(AnalyticVendor).\
                    filter(and_(AnalyticVendor.datestr > datestr_start,
                                AnalyticVendor.datestr <= datestr_end)):
        display_name = ug.vendor.display_name
        key = str(ug.datestr) + display_name
        if key not in cached_cnt:
            cached_cnt[key] = ug.cnt
        if not display_name in cnt_total:
            cnt_total[display_name] = ug.cnt
            continue
        cnt_total[display_name] += ug.cnt

    # find most popular user agent strings
    most_popular = []
    for key, value in sorted(iter(cnt_total.items()), key=lambda k_v: (k_v[1], k_v[0]), reverse=True):
        most_popular.append(key)
        if len(most_popular) >= 6:
            break

    # generate enough for the template
    datasets = []
    palette = [
        'ef4760',   # red
        'ffd160',   # yellow
        '06c990',   # green
        '2f8ba0',   # teal
        '845f80',   # purple
        'ee8510',   # orange
    ]
    idx = 0
    for value in most_popular:
        dataset = {}
        dataset['label'] = value
        dataset['color'] = palette[idx % 6]
        idx += 1
        data = []
        for i in range(timespan_days):
            datestr = _get_datestr_from_datetime(yesterday - datetime.timedelta(days=i))
            key = str(datestr) + value
            dataval = 'NaN'
            if key in cached_cnt:
                dataval = str(cached_cnt[key])
            data.append(dataval)
        dataset['data'] = '[' + ', '.join(data[::-1]) + ']'
        datasets.append(dataset)
    return render_template('analytics-vendor.html',
                           category='analytics',
                           labels_user_agent=_get_chart_labels_days(timespan_days)[::-1],
                           datasets=datasets)
Exemple #4
0
def route_show(firmware_id):
    """ Show firmware information """

    # get details about the firmware
    fw = db.session.query(Firmware).\
            filter(Firmware.firmware_id == firmware_id).\
            first()
    if not fw:
        flash('No firmware matched!', 'danger')
        return redirect(url_for('firmware.route_firmware'))

    # security check
    if not fw.check_acl('@view'):
        flash('Permission denied: Insufficient permissions to view firmware',
              'danger')
        return redirect(url_for('firmware.route_firmware'))

    # get data for the last month or year
    graph_data = []
    graph_labels = None
    if fw.check_acl('@view-analytics') and not fw.do_not_track:
        if fw.timestamp.replace(
                tzinfo=None) > datetime.datetime.today() - datetime.timedelta(
                    days=30):
            datestr = _get_datestr_from_datetime(datetime.date.today() -
                                                 datetime.timedelta(days=31))
            data = db.session.query(AnalyticFirmware.cnt).\
                        filter(AnalyticFirmware.firmware_id == fw.firmware_id).\
                        filter(AnalyticFirmware.datestr > datestr).\
                        order_by(AnalyticFirmware.datestr.desc()).all()
            graph_data = [r[0] for r in data]
            graph_data = graph_data[::-1]
            graph_labels = _get_chart_labels_days(limit=len(data))[::-1]
        else:
            datestr = _get_datestr_from_datetime(datetime.date.today() -
                                                 datetime.timedelta(days=360))
            data = db.session.query(AnalyticFirmware.cnt).\
                        filter(AnalyticFirmware.firmware_id == fw.firmware_id).\
                        filter(AnalyticFirmware.datestr > datestr).\
                        order_by(AnalyticFirmware.datestr.desc()).all()
            # put in month-sized buckets
            for _ in range(12):
                graph_data.append(0)
            cnt = 0
            for res in data:
                graph_data[int(cnt / 30)] += res[0]
                cnt += 1
            graph_data = graph_data[::-1]
            graph_labels = _get_chart_labels_months()[::-1]

    return render_template('firmware-details.html',
                           category='firmware',
                           fw=fw,
                           graph_data=graph_data,
                           graph_labels=graph_labels)
    def _run_test_on_shard(self, test, shard):

        # only Intel μcode supported at this time
        if shard.guid != '3f0229ad-0a00-5269-90cf-0a45d8781b72':
            return

        # get required attributes
        cpuid = shard.get_attr_value('cpuid')
        if not cpuid:
            return
        platform = shard.get_attr_value('platform')
        if not platform:
            return
        version = shard.get_attr_value('version')
        if not version:
            return
        datestr = shard.get_attr_value('yyyymmdd')
        if not datestr:
            return

        # don't expect vendors to include microcode that was released *after*
        # the file was uploaded to the LVFS
        datestr_upload = str(_get_datestr_from_datetime(shard.md.fw.timestamp))

        # load database
        mcefn = self.get_setting('microcode_mcedb_path', required=True)
        if not os.path.exists(mcefn):
            test.add_fail('cannot locate database: {}'.format(mcefn))
            return
        conn = sqlite3.connect(mcefn)
        c = conn.cursor()
        c.execute(
            'SELECT version, yyyymmdd FROM Intel WHERE cpuid=? AND '
            'platform=? AND version>? AND yyyymmdd>? ORDER BY version LIMIT 1',
            (cpuid, platform, version, datestr_upload))
        res = c.fetchone()
        if res:
            (
                newest_version,
                newset_datestr,
            ) = res
            print('CPUID:{:#x} Platform:{:#x} version {:#x} (released on {}) may be older '
                  'than latest released version {:#x} (released on {})'.\
                  format(int(cpuid, 16),
                         int(platform, 16),
                         int(version, 16),
                         datestr,
                         int(newest_version, 16),
                         newset_datestr))
            claim = db.session.query(Claim)\
                              .filter(Claim.kind == 'old-microcode')\
                              .first()
            if claim:
                shard.md.add_claim(claim)
        c.close()
Exemple #6
0
    def run_cron_stats():

        from lvfs import app
        from cron import _generate_stats_for_datestr, _generate_stats
        from lvfs.models import _get_datestr_from_datetime
        with app.test_request_context():
            with io.StringIO() as buf, redirect_stdout(buf):
                _generate_stats_for_datestr(_get_datestr_from_datetime(datetime.date.today()))
                _generate_stats()
                stdout = buf.getvalue()

        assert 'generated' in stdout, stdout
Exemple #7
0
def _generate_stats_for_firmware(fw, datestr):

    # is datestr older than firmware
    if datestr < _get_datestr_from_datetime(fw.timestamp):
        return

    # count how many times any of the firmware files were downloaded
    cnt = _execute_count_star(db.session.query(Client).\
                    filter(Client.firmware_id == fw.firmware_id).\
                    filter(Client.datestr == datestr))
    analytic = AnalyticFirmware(fw.firmware_id, datestr, cnt)
    db.session.add(analytic)
def route_day(offset=1):
    """ A analytics screen to show information about users """
    now = datetime.date.today() - datetime.timedelta(days=offset)
    datestr = _get_datestr_from_datetime(now)
    print(datestr)
    data = [0] * 24
    for ts, in db.session.query(Client.timestamp)\
                         .filter(Client.datestr == datestr):
        data[ts.hour] += 1
    return render_template('analytics-month.html',
                           category='analytics',
                           labels_days=_get_chart_labels_hours(),
                           data_days=data)
    def run_cron_stats():

        from lvfs import app
        from lvfs.main.utils import _regenerate_metrics
        from lvfs.reports.utils import _regenerate_reports
        from lvfs.analytics.utils import _generate_stats_for_datestr
        from lvfs.shards.utils import _regenerate_shard_infos
        from lvfs.models import _get_datestr_from_datetime
        with app.test_request_context():
            with io.StringIO() as buf, redirect_stdout(buf):
                _generate_stats_for_datestr(
                    _get_datestr_from_datetime(datetime.date.today()))
                _regenerate_metrics()
                _regenerate_reports()
                _regenerate_shard_infos()
                stdout = buf.getvalue()

        assert 'generated' in stdout, stdout
def upgrade():
    if 1:
        op.create_table('analytics_vendor',
                        sa.Column('analytic_id', sa.Integer(), nullable=False),
                        sa.Column('datestr', sa.Integer(), nullable=True),
                        sa.Column('vendor_id', sa.Integer(), nullable=False),
                        sa.Column('cnt', sa.Integer(), nullable=True),
                        sa.ForeignKeyConstraint(
                            ['vendor_id'],
                            ['vendors.vendor_id'],
                        ),
                        sa.PrimaryKeyConstraint('analytic_id'),
                        sa.UniqueConstraint('analytic_id'),
                        mysql_character_set='utf8mb4')
        op.create_index(op.f('ix_analytics_vendor_datestr'),
                        'analytics_vendor', ['datestr'],
                        unique=False)
        op.create_index(op.f('ix_analytics_vendor_vendor_id'),
                        'analytics_vendor', ['vendor_id'],
                        unique=False)

    # get all the vendor firmwares
    vendor_fws = {}
    for v in db.session.query(Vendor):
        fw_ids = []
        for fw in v.fws:
            fw_ids.append(fw.firmware_id)
        if not fw_ids:
            continue
        vendor_fws[v.vendor_id] = fw_ids

    # generate the last year of data for each vendor
    now = datetime.date.today() - datetime.timedelta(days=1)
    for _ in range(365):
        datestr = _get_datestr_from_datetime(now)
        for vendor_id in vendor_fws:
            print('processing %s:%s' % (datestr, vendor_id))
            fw_ids = vendor_fws[vendor_id]
            cnt = _execute_count_star(db.session.query(Client).\
                            filter(Client.firmware_id.in_(fw_ids)).\
                            filter(Client.datestr == datestr))
            db.session.add(AnalyticVendor(vendor_id, datestr, cnt))
        now -= datetime.timedelta(days=1)
        db.session.commit()
Exemple #11
0
def route_dashboard():
    user = db.session.query(User).filter(
        User.username == '*****@*****.**').first()
    settings = _get_settings()
    default_admin_password = False
    if user and user.verify_password('Pa$$w0rd'):
        default_admin_password = True

    # get the 10 most recent firmwares
    fws = db.session.query(Firmware).\
                filter(Firmware.user_id == g.user.user_id).\
                join(Remote).filter(Remote.name != 'deleted').\
                order_by(Firmware.timestamp.desc()).limit(10).all()

    download_cnt = 0
    devices_cnt = 0
    appstream_ids = {}
    for fw in g.user.vendor.fws:
        download_cnt += fw.download_cnt
        for md in fw.mds:
            appstream_ids[md.appstream_id] = fw
    devices_cnt = len(appstream_ids)

    # this is somewhat klunky
    data = []
    datestr = _get_datestr_from_datetime(datetime.date.today() -
                                         datetime.timedelta(days=31))
    for cnt in db.session.query(AnalyticVendor.cnt).\
                    filter(AnalyticVendor.vendor_id == g.user.vendor.vendor_id).\
                    filter(AnalyticVendor.datestr > datestr).\
                    order_by(AnalyticVendor.datestr):
        data.append(int(cnt[0]))

    return render_template(
        'dashboard.html',
        fws_recent=fws,
        devices_cnt=devices_cnt,
        download_cnt=download_cnt,
        labels_days=_get_chart_labels_days(limit=len(data))[::-1],
        data_days=data,
        server_warning=settings.get('server_warning', None),
        category='home',
        default_admin_password=default_admin_password)
Exemple #12
0
def _generate_stats_for_vendor(v, datestr):

    # is datestr older than firmware
    if not v.ctime:
        return
    if datestr < _get_datestr_from_datetime(v.ctime - datetime.timedelta(days=1)):
        return

    # get all the firmware for a specific vendor
    fw_ids = [fw.firmware_id for fw in v.fws]
    if not fw_ids:
        return

    # count how many times any of the firmware files were downloaded
    cnt = _execute_count_star(db.session.query(Client).\
                    filter(Client.firmware_id.in_(fw_ids)).\
                    filter(Client.datestr == datestr))
    analytic = AnalyticVendor(vendor_id=v.vendor_id, datestr=datestr, cnt=cnt)
    print('adding %s:%s = %i' % (datestr, v.group_id, cnt))
    db.session.add(analytic)
def upgrade():
    if 1:
        op.add_column('clients',
                      sa.Column('datestr', sa.Integer(), nullable=True))
        op.create_index(op.f('ix_clients_datestr'),
                        'clients', ['datestr'],
                        unique=False)

    # we have to break this into chunks to avoid having 4GB+ of Client objects
    cnt = 0
    while True:
        clients = db.session.query(Client).filter(
            Client.datestr == None).limit(10000).all()
        if not clients:
            break
        for c in clients:
            c.datestr = _get_datestr_from_datetime(c.timestamp)
            cnt += 1
            if cnt % 1000 == 0:
                print(cnt)
                db.session.commit()
    db.session.commit()
def route_month():
    """ A analytics screen to show information about users """

    # this is somewhat klunky
    data = []
    now = datetime.date.today() - datetime.timedelta(days=1)
    for _ in range(30):
        datestr = _get_datestr_from_datetime(now)
        analytic = db.session.query(Analytic).\
                        filter(Analytic.datestr == datestr).\
                        first()
        if analytic:
            data.append(int(analytic.cnt))
        else:
            data.append(0)

        # back one day
        now -= datetime.timedelta(days=1)

    return render_template('analytics-month.html',
                           category='analytics',
                           labels_days=_get_chart_labels_days()[::-1],
                           data_days=data[::-1])
Exemple #15
0
def serveStaticResource(resource):
    """ Return a static image or resource """

    # ban the robots that ignore robots.txt
    user_agent = request.headers.get('User-Agent')
    if user_agent:
        if user_agent.find('MJ12BOT') != -1:
            abort(403)
        if user_agent.find('ltx71') != -1:
            abort(403)
        if user_agent.find('Sogou') != -1:
            abort(403)

    # log certain kinds of files
    if resource.endswith('.cab'):

        # increment the firmware download counter
        fw = db.session.query(Firmware).\
                filter(Firmware.filename == os.path.basename(resource)).\
                options(joinedload('limits')).\
                options(joinedload('vendor')).first()
        if not fw:
            abort(404)

        # check the user agent isn't in the blocklist for this firmware
        for md in fw.mds:
            req = db.session.query(Requirement).\
                            filter(Requirement.component_id == md.component_id).\
                            filter(Requirement.kind == 'id').\
                            filter(Requirement.value == 'org.freedesktop.fwupd').\
                            first()
            if req and user_agent and not _user_agent_safe_for_requirement(
                    user_agent):
                return Response(response='detected fwupd version too old',
                                status=412,
                                mimetype="text/plain")

        # check the firmware vendor has no country block
        if fw.banned_country_codes:
            banned_country_codes = fw.banned_country_codes.split(',')
            geo = GeoIP.new(GeoIP.GEOIP_MEMORY_CACHE)
            country_code = geo.country_code_by_addr(_get_client_address())
            if country_code and country_code in banned_country_codes:
                return Response(
                    response='firmware not available from this IP range',
                    status=451,
                    mimetype="text/plain")

        # check any firmware download limits
        for fl in fw.limits:
            if not fl.user_agent_glob or fnmatch.fnmatch(
                    user_agent, fl.user_agent_glob):
                datestr = _get_datestr_from_datetime(datetime.date.today() -
                                                     datetime.timedelta(1))
                cnt = _execute_count_star(db.session.query(Client).\
                            filter(Client.firmware_id == fw.firmware_id).\
                            filter(Client.datestr >= datestr))
                if cnt >= fl.value:
                    response = fl.response
                    if not response:
                        response = 'Too Many Requests'
                    resp = Response(response=response,
                                    status=429,
                                    mimetype='text/plain')
                    resp.headers['Retry-After'] = '86400'
                    return resp

        # this is cached for easy access on the firmware details page
        if not fw.do_not_track:
            fw.download_cnt += 1

        # log the client request
        if not fw.do_not_track:
            db.session.add(
                Client(addr=_addr_hash(_get_client_address()),
                       firmware_id=fw.firmware_id,
                       user_agent=user_agent))
            db.session.commit()

    # firmware blobs
    if resource.startswith('downloads/'):
        return send_from_directory(app.config['DOWNLOAD_DIR'],
                                   os.path.basename(resource))
    if resource.startswith('deleted/'):
        return send_from_directory(app.config['RESTORE_DIR'],
                                   os.path.basename(resource))
    if resource.startswith('uploads/'):
        return send_from_directory(app.config['UPLOAD_DIR'],
                                   os.path.basename(resource))

    # static files served locally
    return send_from_directory(os.path.join(app.root_path, 'static'), resource)
Exemple #16
0
 if 'fwchecks' in sys.argv:
     try:
         with app.test_request_context():
             _check_firmware()
             _yara_query_all()
     except NotImplementedError as e:
         print(str(e))
         sys.exit(1)
 if 'stats' in sys.argv:
     try:
         with app.test_request_context():
             # default to yesterday, but also allow specifying the offset
             days = 1
             if len(sys.argv) > 2:
                 days = int(sys.argv[2])
             val = _get_datestr_from_datetime(datetime.date.today() -
                                              datetime.timedelta(days=days))
             _generate_stats_for_datestr(val)
             _generate_stats()
     except NotImplementedError as e:
         print(str(e))
         sys.exit(1)
 if 'statsmigrate' in sys.argv:
     try:
         update_kinds = None
         if len(sys.argv) > 2:
             update_kinds = sys.argv[2:]
         with app.test_request_context():
             for days in range(1, 720):
                 val = _get_datestr_from_datetime(datetime.date.today() -
                                                  datetime.timedelta(
                                                      days=days))
def _async_generate_stats():
    datestr = _get_datestr_from_datetime(datetime.date.today() -
                                         datetime.timedelta(days=1))
    _generate_stats_for_datestr(datestr)
def route_vendor(timespan_days=30, vendor_cnt=6, smoothing=0):
    """ A analytics screen to show information about users """

    # get data for this time period
    cnt_total = defaultdict(int)
    cached_cnt = defaultdict(int)
    yesterday = datetime.date.today() - datetime.timedelta(days=1)
    datestr_start = _get_datestr_from_datetime(yesterday - datetime.timedelta(
        days=timespan_days))
    datestr_end = _get_datestr_from_datetime(yesterday)
    print('get data for', datestr_start, datestr_end)
    for ug in db.session.query(AnalyticVendor)\
                        .filter(and_(AnalyticVendor.datestr >= datestr_start,
                                     AnalyticVendor.datestr <= datestr_end)):
        display_name = ug.vendor.display_name
        key = str(ug.datestr) + display_name
        cached_cnt[key] += ug.cnt
        cnt_total[display_name] += ug.cnt

    # find most popular user agent strings
    most_popular = []
    for key, value in sorted(iter(cnt_total.items()),
                             key=lambda k_v: (k_v[1], k_v[0]),
                             reverse=True):
        most_popular.append(key)
        if len(most_popular) >= vendor_cnt:
            break

    # optionally smooth
    if not smoothing:
        smoothing = int(timespan_days / 25)

    # generate enough for the template
    datasets = []
    palette = [
        'ef4760',  # red
        'ffd160',  # yellow
        '06c990',  # green
        '2f8ba0',  # teal
        '845f80',  # purple
        'ee8510',  # orange
    ]
    idx = 0
    for value in sorted(most_popular):
        dataset = {}
        dataset['label'] = value
        dataset['color'] = palette[idx % 6]
        idx += 1
        data = []
        for i in range(timespan_days, 0, -1):
            datestr = _get_datestr_from_datetime(yesterday -
                                                 datetime.timedelta(days=i))
            key = str(datestr) + value
            dataval = 0
            if key in cached_cnt:
                dataval = cached_cnt[key]
            data.append(dataval)
        if smoothing > 1:
            data = _running_mean(data, smoothing)
        dataset['data'] = json.dumps(data)
        datasets.append(dataset)

    return render_template(
        'analytics-vendor.html',
        category='analytics',
        labels_user_agent=_get_chart_labels_days(timespan_days)[::-1],
        datasets=datasets)
def route_reportattrs_kind(kind, timespan_days=90):
    """ A analytics screen to show information about users """

    # get data for this time period
    cnt_total = {}
    yesterday = datetime.date.today() - datetime.timedelta(days=1)
    datestr_start = yesterday - datetime.timedelta(days=timespan_days)
    cnt_total = {}
    cached_cnt = {}
    for report_ts, attr_val in db.session.query(Report.timestamp,
                                                ReportAttribute.value)\
                          .filter(ReportAttribute.key == kind)\
                          .join(Report)\
                          .filter(and_(Report.timestamp > datestr_start,
                                       Report.timestamp <= yesterday)):

        key = str(_get_datestr_from_datetime(report_ts)) + attr_val
        if key not in cached_cnt:
            cached_cnt[key] = 1
        else:
            cached_cnt[key] += 1
        if not attr_val in cnt_total:
            cnt_total[attr_val] = 1
            continue
        cnt_total[attr_val] += 1

    # find most popular user agent strings
    most_popular = []
    for key, value in sorted(iter(cnt_total.items()),
                             key=lambda k_v: (k_v[1], k_v[0]),
                             reverse=True):
        most_popular.append(key)
        if len(most_popular) >= 6:
            break

    # generate enough for the template
    datasets = []
    palette = [
        'ef4760',  # red
        'ffd160',  # yellow
        '06c990',  # green
        '2f8ba0',  # teal
        '845f80',  # purple
        'ee8510',  # orange
    ]
    idx = 0
    for value in most_popular:
        dataset = {}
        dataset['label'] = value
        dataset['color'] = palette[idx % 6]
        idx += 1
        data = []
        for i in range(timespan_days):
            datestr = _get_datestr_from_datetime(yesterday -
                                                 datetime.timedelta(days=i))
            key = str(datestr) + value
            dataval = 'NaN'
            if key in cached_cnt:
                dataval = str(cached_cnt[key])
            data.append(dataval)
        dataset['data'] = '[' + ', '.join(data[::-1]) + ']'
        datasets.append(dataset)
    return render_template(
        'analytics-reportattrs-kind.html',
        category='analytics',
        kind=kind,
        labels_user_agent=_get_chart_labels_days(timespan_days)[::-1],
        datasets=datasets)
def route_user_agents(kind='APP', timespan_days=30):
    """ A analytics screen to show information about users """

    # map back to UseragentKind
    try:
        kind_enum = UseragentKind[kind]
    except KeyError as e:
        flash('Unable to view analytic type: {}'.format(str(e)), 'danger')
        return redirect(url_for('analytics.route_user_agents'))

    # get data for this time period
    cnt_total = {}
    cached_cnt = {}
    yesterday = datetime.date.today() - datetime.timedelta(days=1)
    datestr_start = _get_datestr_from_datetime(yesterday - datetime.timedelta(
        days=timespan_days))
    datestr_end = _get_datestr_from_datetime(yesterday)
    for ug in db.session.query(Useragent).\
                    filter(Useragent.kind == kind_enum.value).\
                    filter(and_(Useragent.datestr > datestr_start,
                                Useragent.datestr <= datestr_end)):
        user_agent_safe = _user_agent_wildcard(ug.value)
        if kind == 'FWUPD':
            splt = user_agent_safe.split('.', 3)
            if len(splt) == 3:
                user_agent_safe = '{}.{}.x'.format(splt[0], splt[1])
        key = str(ug.datestr) + user_agent_safe
        if key not in cached_cnt:
            cached_cnt[key] = ug.cnt
        else:
            cached_cnt[key] += ug.cnt
        if not user_agent_safe in cnt_total:
            cnt_total[user_agent_safe] = ug.cnt
            continue
        cnt_total[user_agent_safe] += ug.cnt

    # find most popular user agent strings
    most_popular = []
    for key, value in sorted(iter(cnt_total.items()),
                             key=lambda k_v: (k_v[1], k_v[0]),
                             reverse=True):
        most_popular.append(key)
        if len(most_popular) >= 6:
            break

    # generate enough for the template
    datasets = []
    palette = [
        'ef4760',  # red
        'ffd160',  # yellow
        '06c990',  # green
        '2f8ba0',  # teal
        '845f80',  # purple
        'ee8510',  # orange
    ]
    idx = 0
    for value in most_popular:
        dataset = {}
        dataset['label'] = value
        dataset['color'] = palette[idx % 6]
        idx += 1
        data = []
        for i in range(timespan_days):
            datestr = _get_datestr_from_datetime(yesterday -
                                                 datetime.timedelta(days=i))
            key = str(datestr) + value
            dataval = 'NaN'
            if key in cached_cnt:
                dataval = str(cached_cnt[key])
            data.append(dataval)
        dataset['data'] = '[' + ', '.join(data[::-1]) + ']'
        datasets.append(dataset)
    return render_template(
        'analytics-user-agent.html',
        category='analytics',
        kind=kind,
        labels_user_agent=_get_chart_labels_days(timespan_days)[::-1],
        datasets=datasets)
    def _run_test_on_shard(self, test, shard):

        # only Intel μcode supported at this time
        if shard.guid != '3f0229ad-0a00-5269-90cf-0a45d8781b72':
            return

        # get required attributes
        cpuid = shard.get_attr_value('cpuid')
        if not cpuid:
            return
        platform = shard.get_attr_value('platform')
        if not platform:
            return
        version = shard.get_attr_value('version')
        if not version:
            return
        datestr = shard.get_attr_value('yyyymmdd')
        if not datestr:
            return

        # don't expect vendors to include microcode that was released *after*
        # the file was uploaded to the LVFS
        datestr_upload = str(_get_datestr_from_datetime(shard.md.fw.timestamp))

        # find any higher microcode version larger than this one known to the LVFS
        stmt1 = db.session.query(ComponentShard.component_shard_id)\
                          .join(ComponentShardAttribute)\
                          .filter(ComponentShardAttribute.key == 'cpuid')\
                          .filter(ComponentShardAttribute.value == cpuid)\
                          .subquery()
        stmt2 = db.session.query(ComponentShard.component_shard_id)\
                          .join(ComponentShardAttribute)\
                          .filter(ComponentShardAttribute.key == 'platform')\
                          .filter(ComponentShardAttribute.value == platform)\
                          .subquery()
        stmt3 = db.session.query(ComponentShard.component_shard_id)\
                          .join(ComponentShardAttribute)\
                          .filter(ComponentShardAttribute.key == 'yyyymmdd')\
                          .filter(ComponentShardAttribute.value < datestr_upload)\
                          .subquery()
        shards = db.session.query(ComponentShard)\
                           .join(stmt1, ComponentShard.component_shard_id == stmt1.c.component_shard_id)\
                           .join(stmt2, ComponentShard.component_shard_id == stmt2.c.component_shard_id)\
                           .join(stmt3, ComponentShard.component_shard_id == stmt3.c.component_shard_id)\
                           .join(ComponentShardAttribute)\
                           .filter(ComponentShardAttribute.key == 'version')\
                           .filter(ComponentShardAttribute.value > version)\
                           .order_by(ComponentShardAttribute.value)\
                           .all()
        for shard_tmp in shards:
            if shard_tmp.md.fw.remote.is_public:

                # an update can be created for resolving vendor-specific or
                # model-specific issues, so restrict results to the AppStream ID
                if shard.md.appstream_id != shard_tmp.md.appstream_id:
                    continue

                # only count firmware older than the correct firmware
                if shard.md < shard_tmp.md:
                    continue

                newest_version = shard_tmp.get_attr_value('version')
                newset_datestr = shard_tmp.get_attr_value('yyyymmdd')
                test.add_fail('Downgraded Intel CPU microcode detected',
                              'CPUID:{:#x} Platform:{:#x} version {:#x} (released on {}) is older '
                              'than latest released version {:#x} (released on {}) found in {} v{}'\
                              .format(int(cpuid, 16),
                                      int(platform, 16),
                                      int(version, 16),
                                      datestr,
                                      int(newest_version, 16),
                                      newset_datestr,
                                      shard_tmp.md.name_with_category,
                                      shard_tmp.md.version_display))
                return