示例#1
0
def edit_user(username):
    own_user = username == current_user.name
    if not current_user.role.is_administrator and not own_user:
        forbidden()

    user = User.query.filter_by(name=username).first()
    if not user:
        return not_found()

    form = UserForm(edit=True)
    if not form.is_submitted():
        form.username.data = user.name
        form.email.data = user.email
        form.role.data = user.role.name
        form.active.data = user.active
    if not form.validate_on_submit():
        return render_template('admin/form/user.html',
                               title='Edit {}'.format(username),
                               form=form,
                               User=User,
                               random_password=True,
                               password_length={
                                   'min': TRACKER_PASSWORD_LENGTH_MIN,
                                   'max': TRACKER_PASSWORD_LENGTH_MAX
                               })

    active_admins = User.query.filter_by(active=True,
                                         role=UserRole.administrator).count()
    if user.id == current_user.id and 1 == active_admins and not form.active.data:
        return forbidden()

    user.name = form.username.data
    user.email = form.email.data
    user.role = UserRole.fromstring(form.role.data)
    if form.random_password.data:
        form.password.data = random_string()
    if form.password.data and 0 != len(form.password.data):
        user.salt = random_string()
        user.password = hash_password(form.password.data, user.salt)
    user.active = form.active.data
    user_invalidate(user)
    db.session.commit()

    flash_password = ''
    if form.random_password.data:
        flash_password = '******'.format(form.password.data)
    flash('Edited user {}{}'.format(user.name, flash_password))
    return redirect('/user')
示例#2
0
def delete_user(username):
    user = User.query.filter_by(name=username).first()
    if not user:
        return not_found()

    form = ConfirmForm()
    title = 'Delete {}'.format(username)
    if not form.validate_on_submit():
        return render_template('admin/form/delete_user.html',
                               title=title,
                               heading=title,
                               form=form,
                               user=user)

    if not form.confirm.data:
        return redirect('/user')

    active_admins = User.query.filter_by(active=True,
                                         role=UserRole.administrator).count()
    if user.id == current_user.id and 1 >= active_admins:
        return forbidden()

    user_invalidate(user)
    db.session.delete(user)
    db.session.commit()
    flash('Deleted user {}'.format(user.name))
    return redirect('/user')
示例#3
0
def delete_advisory(advisory_id):
    advisory, pkg, group = (db.session.query(Advisory, CVEGroupPackage, CVEGroup)
                            .filter(Advisory.id == advisory_id)
                            .join(CVEGroupPackage).join(CVEGroup)).first()

    if not advisory:
        return not_found()

    if Publication.scheduled != advisory.publication:
        return forbidden()

    form = ConfirmForm()
    title = 'Delete {}'.format(advisory.id)
    if not form.validate_on_submit():
        return render_template('form/delete_advisory.html',
                               title=title,
                               heading=title,
                               form=form,
                               advisory=advisory,
                               pkg=pkg,
                               group=group)

    if not form.confirm.data:
        return redirect('/{}'.format(advisory.id))

    db.session.delete(advisory)
    db.session.commit()
    flash('Deleted {}'.format(advisory.id))
    return redirect('/')
示例#4
0
def delete_group(avg):
    avg_id = avg.replace('AVG-', '')
    entries = (db.session.query(
        CVEGroup, CVE,
        CVEGroupPackage, Advisory).filter(CVEGroup.id == avg_id).join(
            CVEGroupEntry, CVEGroup.issues).join(CVE, CVEGroupEntry.cve).join(
                CVEGroupPackage, CVEGroup.packages).outerjoin(
                    Advisory,
                    Advisory.group_package_id == CVEGroupPackage.id)).all()
    if not entries:
        return not_found()

    group = entries[0][0]
    issues = set()
    packages = set()
    advisories = set()
    for group, issue, pkg, advisory in entries:
        issues.add(issue)
        packages.add(pkg)
        if advisory:
            advisories.add(advisory)

    if not user_can_delete_group(advisories):
        return forbidden()

    issues = sorted(issues, key=lambda item: item.id)
    packages = sorted(packages, key=lambda item: item.pkgname)
    advisories = sorted(advisories, key=lambda item: item.id, reverse=True)

    form = ConfirmForm()
    title = 'Delete {}'.format(avg)
    if not form.validate_on_submit():
        return render_template('form/delete_group.html',
                               title=title,
                               heading=title,
                               form=form,
                               group=group,
                               issues=issues,
                               packages=packages)

    if not form.confirm.data:
        return redirect('/{}'.format(group))

    db.session.delete(group)
    db.session.commit()
    flash('Deleted {}'.format(group))
    return redirect('/')
示例#5
0
def delete_issue(issue):
    entries = (db.session.query(CVE, CVEGroup, CVEGroupPackage, Advisory)
               .filter(CVE.id == issue)
               .outerjoin(CVEGroupEntry).outerjoin(CVEGroup).outerjoin(CVEGroupPackage)
               .outerjoin(Advisory, Advisory.group_package_id == CVEGroupPackage.id)
               .order_by(CVEGroup.created.desc()).order_by(CVEGroupPackage.pkgname)).all()
    if not entries:
        return not_found()

    issue = entries[0][0]
    advisories = set()
    groups = set()
    group_packages = defaultdict(set)
    for cve, group, pkg, advisory in entries:
        if group:
            groups.add(group)
            group_packages[group].add(pkg.pkgname)
        if advisory:
            advisories.add(advisory)

    if not user_can_delete_issue(advisories):
        return forbidden()

    group_ids = [group.id for group in groups]

    group_entries = (db.session.query(CVEGroup, CVE)
                     .join(CVEGroupEntry).join(CVE)
                     .order_by(CVE.id.desc()))
    if group_ids:
        group_entries = group_entries.filter(CVEGroup.id.in_(group_ids))
    group_entries = group_entries.all()

    group_issues = defaultdict(set)
    for group, cve in group_entries:
        group_issues[group].add(cve)

    groups = sorted(groups, key=lambda item: item.created, reverse=True)
    groups = sorted(groups, key=lambda item: item.status)
    group_packages = dict(map(lambda item: (item[0], sorted(item[1])), group_packages.items()))

    form = ConfirmForm()
    title = 'Delete {}'.format(issue)
    if not form.validate_on_submit():
        return render_template('form/delete_cve.html',
                               title=title,
                               heading=title,
                               form=form,
                               issue=issue,
                               groups=groups,
                               group_packages=group_packages,
                               group_issues=group_issues)

    if not form.confirm.data:
        return redirect('/{}'.format(issue))

    # delete groups that only contain this issue
    for group, issues in group_issues.items():
        if 0 == len(list(filter(lambda e: e.id != issue.id, issues))):
            flash('Deleted {}'.format(group))
            db.session.delete(group)

    db.session.delete(issue)
    db.session.commit()
    flash('Deleted {}'.format(issue))
    return redirect('/')
示例#6
0
def sso_auth():
    try:
        token = oauth.idp.authorize_access_token()
        parsed_token = oauth.idp.parse_id_token(token)
    except AuthlibBaseError as e:
        return bad_request(f'{e.description}')

    idp_user_sub = parsed_token.get('sub')
    if not idp_user_sub:
        return bad_request(LOGIN_ERROR_MISSING_USER_SUB_FROM_TOKEN)

    idp_email_verified = parsed_token.get('email_verified')
    if not idp_email_verified:
        return forbidden(LOGIN_ERROR_EMAIL_ADDRESS_NOT_VERIFIED)

    idp_email = parsed_token.get('email')
    if not idp_email:
        return bad_request(LOGIN_ERROR_MISSING_EMAIL_FROM_TOKEN)

    idp_username = parsed_token.get('preferred_username')
    if not idp_username:
        return bad_request(LOGIN_ERROR_MISSING_USERNAME_FROM_TOKEN)

    idp_groups = parsed_token.get('groups')
    if idp_groups is None:
        return bad_request(LOGIN_ERROR_MISSING_GROUPS_FROM_TOKEN)

    user_role = get_user_role_from_idp_groups(idp_groups)
    if not user_role:
        return forbidden(LOGIN_ERROR_PERMISSION_DENIED)

    # get local user from current authenticated idp id
    user = db.get(User, idp_id=idp_user_sub)

    if not user:
        # get local user from idp email address
        user = db.get(User, email=idp_email)
        if user:
            # prevent impersonation by checking whether this email is associated with an idp id
            if user.idp_id:
                return forbidden(
                    LOGIN_ERROR_EMAIL_ASSOCIATED_WITH_DIFFERENT_SUB)
            # email is already associated with a different username
            if user.name != idp_username:
                return forbidden(
                    LOGIN_ERROR_EMAIL_ASSOCIATED_WITH_DIFFERENT_USERNAME)
        # prevent integrity error for mismatching mail between db and keycloak
        check_user = db.get(User, name=idp_username)
        if check_user and check_user.email != idp_email:
            return forbidden(
                LOGIN_ERROR_USERNAME_ASSOCIATE_WITH_DIFFERENT_EMAIL)

    if user:
        user.role = user_role
        user.email = idp_email
    else:
        salt = random_string()
        user = db.create(User,
                         name=idp_username,
                         email=idp_email,
                         salt=salt,
                         password=hash_password(
                             random_string(TRACKER_PASSWORD_LENGTH_MAX), salt),
                         role=user_role,
                         active=True,
                         idp_id=idp_user_sub)
        db.session.add(user)

    db.session.commit()
    user = user_assign_new_token(user)
    user.is_authenticated = True
    login_user(user)

    return redirect(url_for('tracker.index'))
示例#7
0
def add_cve():
    form = CVEForm()
    if not form.validate_on_submit():
        return render_template('form/cve.html',
                               title='Add CVE',
                               form=form,
                               CVE=CVE)

    cve = db.get(CVE, id=form.cve.data)
    if cve is not None:
        advisories = (db.session.query(Advisory).join(
            CVEGroupEntry, CVEGroupEntry.cve_id == cve.id).join(
                CVEGroup, CVEGroupEntry.group).join(
                    CVEGroupPackage, CVEGroup.packages).filter(
                        Advisory.group_package_id == CVEGroupPackage.id)
                      ).all()
        if not user_can_edit_issue(advisories):
            flash(ERROR_ISSUE_REFERENCED_BY_ADVISORY.format(cve.id), 'error')
            return forbidden()

        not_merged = []
        merged = False

        # try to merge issue_type
        if 'unknown' != form.issue_type.data:
            if 'unknown' == cve.issue_type:
                cve.issue_type = form.issue_type.data
                merged = True
            elif form.issue_type.data != cve.issue_type:
                not_merged.append(form.issue_type)
        form.issue_type.data = cve.issue_type

        # try to merge severity
        form_severity = Severity.fromstring(form.severity.data)
        if Severity.unknown != form_severity:
            if Severity.unknown == cve.severity:
                cve.severity = form_severity
                merged = True
            elif form_severity != cve.severity:
                not_merged.append(form.severity)
        form.severity.data = cve.severity.name

        # try to merge remote
        form_remote = Remote.fromstring(form.remote.data)
        if Remote.unknown != form_remote:
            if Remote.unknown == cve.remote:
                cve.remote = form_remote
                merged = True
            elif form_remote != cve.remote:
                not_merged.append(form.remote)
        form.remote.data = cve.remote.name

        # try to merge description
        if form.description.data:
            if not cve.description:
                cve.description = form.description.data
                merged = True
            elif form.description.data != cve.description:
                not_merged.append(form.description)
        form.description.data = cve.description

        # try to merge references
        references = cve.reference.splitlines() if cve.reference else []
        old_references = references.copy()
        form_references = form.reference.data.splitlines(
        ) if form.reference.data else []
        for reference in form_references:
            if reference not in references:
                references.append(reference)
                merged = True
        if old_references != references:
            cve.reference = '\n'.join(references)
        form.reference.data = cve.reference

        # try to merge notes
        if form.notes.data:
            if not cve.notes:
                cve.notes = form.notes.data
                merged = True
            elif form.notes.data != cve.notes:
                not_merged.append(form.notes)
        form.notes.data = cve.notes

        # if something got merged, commit and flash
        if merged:
            db.session.commit()
            flash(CVE_MERGED.format(cve.id))

        # warn if something failed to be merged
        if not_merged:
            for field in not_merged:
                field.errors.append(ERROR_UNMERGEABLE)

            not_merged_labels = [field.label.text for field in not_merged]
            flash(
                CVE_MERGED_PARTIALLY.format(cve.id,
                                            ', '.join(not_merged_labels)),
                'warning')
            return render_template('form/cve.html',
                                   title='Edit {}'.format(cve),
                                   form=form,
                                   CVE=CVE,
                                   action='{}/edit'.format(cve.id))

        return redirect('/{}'.format(cve.id))

    cve = CVE()
    cve.id = form.cve.data
    cve.issue_type = form.issue_type.data
    cve.description = form.description.data
    cve.severity = Severity.fromstring(form.severity.data)
    cve.remote = Remote.fromstring(form.remote.data)
    cve.reference = form.reference.data
    cve.notes = form.notes.data
    db.session.add(cve)
    db.session.commit()
    flash('Added {}'.format(cve.id))
    return redirect('/{}'.format(cve.id))
示例#8
0
def edit_cve(cve):
    entries = (db.session.query(CVE, CVEGroup, Advisory)
               .filter(CVE.id == cve)
               .outerjoin(CVEGroupEntry, CVEGroupEntry.cve_id == CVE.id)
               .outerjoin(CVEGroup, CVEGroupEntry.group)
               .outerjoin(CVEGroupPackage, CVEGroup.packages)
               .outerjoin(Advisory, Advisory.group_package_id == CVEGroupPackage.id)).all()
    if not entries:
        return not_found()

    cve = entries[0][0]
    groups = set(group for (cve, group, advisory) in entries if group)
    advisories = set(advisory for (cve, group, advisory) in entries if advisory)

    if not user_can_edit_issue(advisories):
        return forbidden()

    form = CVEForm(edit=True)
    if not form.is_submitted():
        form.cve.data = cve.id
        form.issue_type.data = cve.issue_type
        form.description.data = cve.description
        form.severity.data = cve.severity.name
        form.remote.data = cve.remote.name
        form.reference.data = cve.reference
        form.notes.data = cve.notes
    if not form.validate_on_submit():
        if advisories:
            flash('WARNING: This is referenced by an already published advisory!', 'warning')
        return render_template('form/cve.html',
                               title='Edit {}'.format(cve),
                               form=form,
                               CVE=CVE)

    severity = Severity.fromstring(form.severity.data)
    severity_changed = cve.severity != severity
    issue_type_changed = cve.issue_type != form.issue_type.data

    cve.issue_type = form.issue_type.data
    cve.description = form.description.data
    cve.severity = severity
    cve.remote = Remote.fromstring(form.remote.data)
    cve.reference = form.reference.data
    cve.notes = form.notes.data

    if severity_changed or issue_type_changed:
        # update cached group severity for all goups containing this issue
        group_ids = [group.id for group in groups]
        issues = (db.session.query(CVEGroup, CVE)
                  .join(CVEGroupEntry, CVEGroup.issues)
                  .join(CVE, CVEGroupEntry.cve)
                  .group_by(CVEGroup.id).group_by(CVE.id))
        if group_ids:
            issues = issues.filter(CVEGroup.id.in_(group_ids))
        issues = (issues).all()

        if severity_changed:
            group_severity = defaultdict(list)
            for group, issue in issues:
                group_severity[group].append(issue.severity)
            for group, severities in group_severity.items():
                group.severity = highest_severity(severities)

        # update scheduled advisories if the issue type changes
        if advisories and issue_type_changed:
            group_issue_type = defaultdict(set)
            for group, issue in issues:
                group_issue_type[group].add(issue.issue_type)
            for advisory in advisories:
                if Publication.published == advisory.publication:
                    continue
                issue_types = group_issue_type[advisory.group_package.group]
                issue_type = 'multiple issues' if len(issue_types) > 1 else next(iter(issue_types))
                advisory.advisory_type = issue_type

    if db.session.is_modified(cve):
        flash('Edited {}'.format(cve.id))

    db.session.commit()
    return redirect('/{}'.format(cve.id))
示例#9
0
def edit_group(avg):
    group_id = avg.replace('AVG-', '')
    group_data = (db.session.query(CVEGroup, CVE, func.group_concat(CVEGroupPackage.pkgname, ' '), Advisory)
                  .filter(CVEGroup.id == group_id)
                  .join(CVEGroupEntry, CVEGroup.issues)
                  .join(CVE, CVEGroupEntry.cve)
                  .join(CVEGroupPackage, CVEGroup.packages)
                  .outerjoin(Advisory, Advisory.group_package_id == CVEGroupPackage.id)
                  .group_by(CVEGroup.id).group_by(CVE.id).group_by(CVEGroupPackage.pkgname)
                  .order_by(CVE.id)).all()
    if not group_data:
        return not_found()

    group = group_data[0][0]
    issues = set([cve for (group, cve, pkg, advisory) in group_data])
    issue_ids = set([cve.id for cve in issues])
    pkgnames = set(chain.from_iterable([pkg.split(' ') for (group, cve, pkg, advisory) in group_data]))
    advisories = set(advisory for (group, cve, pkg, advisory) in group_data if advisory)

    if not user_can_edit_group(advisories):
        return forbidden()

    form = GroupForm(pkgnames)
    if not form.is_submitted():
        form.affected.data = group.affected
        form.fixed.data = group.fixed
        form.pkgnames.data = "\n".join(sorted(pkgnames))
        form.status.data = status_to_affected(group.status).name
        form.reference.data = group.reference
        form.notes.data = group.notes
        form.bug_ticket.data = group.bug_ticket
        form.advisory_qualified.data = group.advisory_qualified and group.status is not Status.not_affected

        issue_ids = sorted(issue_ids, key=issue_to_numeric)
        form.cve.data = "\n".join(issue_ids)
    if not form.validate_on_submit():
        if advisories:
            flash('WARNING: This is referenced by an already published advisory!', 'warning')
        return render_template('form/group.html',
                               title='Edit {}'.format(avg),
                               form=form,
                               CVEGroup=CVEGroup)

    pkgnames_edited = multiline_to_list(form.pkgnames.data)
    group.affected = form.affected.data
    group.fixed = form.fixed.data
    group.status = affected_to_status(Affected.fromstring(form.status.data), pkgnames_edited[0], group.fixed)
    group.bug_ticket = form.bug_ticket.data
    group.reference = form.reference.data
    group.notes = form.notes.data
    group.advisory_qualified = form.advisory_qualified.data and group.status is not Status.not_affected

    cve_ids = multiline_to_list(form.cve.data)
    cve_ids = set(filter(lambda s: s.startswith('CVE-'), cve_ids))
    issues_removed = set(filter(lambda issue: issue not in cve_ids, issue_ids))
    issues_added = set(filter(lambda issue: issue not in issue_ids, cve_ids))
    issues_final = set(filter(lambda issue: issue.id not in issues_removed, issues))
    issues_changed = any(issues_added) or any(issues_removed)

    # remove old issues
    for issue in filter(lambda issue: issue.cve_id in issues_removed, list(group.issues)):
        group.issues.remove(issue)
        flash('Removed {}'.format(issue.cve_id))

    # add new issues
    severities = [issue.severity for issue in list(filter(lambda issue: issue.id not in issues_removed, issues))]
    for cve_id in issues_added:
        # TODO check if we can avoid this by the latter append call
        cve = db.get(CVE, id=cve_id)
        if not cve:
            cve = CVE.new(id=cve_id)
        db.get_or_create(CVEGroupEntry, group=group, cve=cve)
        flash('Added {}'.format(cve.id))

        severities.append(cve.severity)
        issues_final.add(cve)
    group.severity = highest_severity(severities)

    pkgnames_removed = set(filter(lambda pkgname: pkgname not in pkgnames_edited, pkgnames))
    pkgnames_added = set(filter(lambda pkgname: pkgname not in pkgnames, pkgnames_edited))
    pkgnames_changed = any(pkgnames_removed) or any(pkgnames_added)

    # remove old packages
    for pkg in filter(lambda pkg: pkg.pkgname in pkgnames_removed, list(group.packages)):
        group.packages.remove(pkg)
        flash('Removed {}'.format(pkg.pkgname))

    #  add new packages
    for pkgname in pkgnames_added:
        db.get_or_create(CVEGroupPackage, pkgname=pkgname, group=group)
        flash('Added {}'.format(pkgname))

    # update scheduled advisories
    for advisory in advisories:
        if Publication.published == advisory.publication:
            continue
        issue_type = 'multiple issues' if len(set([issue.issue_type for issue in issues_final])) > 1 else next(iter(issues_final)).issue_type
        advisory.advisory_type = issue_type

    # update changed date on modification
    if pkgnames_changed or issues_changed or db.session.is_modified(group):
        group.changed = datetime.utcnow()
        flash('Edited {}'.format(group.name))

    db.session.commit()
    return redirect('/{}'.format(group.name))
示例#10
0
 def decorated_view(*args, **kwargs):
     if not permission.fget(current_user.role):
         from tracker.view.error import forbidden
         return forbidden()
     return func(*args, **kwargs)
示例#11
0
def edit_cve(cve):
    entries = (db.session.query(
        CVE, CVEGroup, Advisory).filter(CVE.id == cve).outerjoin(
            CVEGroupEntry, CVEGroupEntry.cve_id == CVE.id).outerjoin(
                CVEGroup, CVEGroupEntry.group).outerjoin(
                    CVEGroupPackage, CVEGroup.packages).outerjoin(
                        Advisory,
                        Advisory.group_package_id == CVEGroupPackage.id)
               ).all()
    if not entries:
        return not_found()

    cve: CVE = entries[0][0]
    groups = set(group for (cve, group, advisory) in entries if group)
    advisories = set(advisory for (cve, group, advisory) in entries
                     if advisory)

    if not user_can_edit_issue(advisories):
        flash(ERROR_ISSUE_REFERENCED_BY_ADVISORY.format(cve.id), 'error')
        return forbidden()

    form = CVEForm(edit=True)
    if not form.is_submitted():
        form.cve.data = cve.id
        form.issue_type.data = cve.issue_type
        form.description.data = cve.description
        form.severity.data = cve.severity.name
        form.remote.data = cve.remote.name
        form.reference.data = cve.reference
        form.notes.data = cve.notes
        form.changed.data = str(cve.changed)
        form.changed_latest.data = str(cve.changed)

    concurrent_modification = str(cve.changed) != form.changed.data

    if not form.validate_on_submit() or (
            concurrent_modification
            and not (form.force_submit.data
                     and str(cve.changed) == form.changed_latest.data)):
        if advisories:
            flash(
                'WARNING: This is referenced by an already published advisory!',
                'warning')

        issue = None
        code = 200
        if concurrent_modification:
            flash('WARNING: The remote data has changed!', 'warning')
            code = Conflict.code

            issue = CVE()
            issue.id = form.cve.data
            issue.issue_type = form.issue_type.data
            issue.issue_type_mod = cve.issue_type != issue.issue_type
            issue.description = form.description.data
            issue.description_mod = cve.description != issue.description
            issue.severity = Severity.fromstring(form.severity.data)
            issue.severity_mod = cve.severity != issue.severity
            issue.remote = Remote.fromstring(form.remote.data)
            issue.remote_mod = cve.remote != issue.remote
            issue.reference = form.reference.data
            issue.reference_mod = cve.reference != issue.reference
            issue.notes = form.notes.data
            issue.notes_mod = cve.notes != issue.notes

            if form.changed_latest.data != cve.changed:
                form.force_submit.data = False
            form.changed_latest.data = str(cve.changed)

            Transaction = versioning_manager.transaction_cls
            VersionClassCVE = version_class(CVE)
            version = (db.session.query(
                Transaction, VersionClassCVE).outerjoin(
                    VersionClassCVE,
                    Transaction.id == VersionClassCVE.transaction_id).filter(
                        VersionClassCVE.id == cve.id).order_by(
                            Transaction.issued_at.desc())).first()[1]
            issue.transaction = version.transaction
            issue.operation_type = version.operation_type
            issue.previous = cve

        return render_template(
            'form/cve.html',
            title='Edit {}'.format(cve),
            form=form,
            CVE=CVE,
            issue=issue,
            concurrent_modification=concurrent_modification,
            can_watch_user_log=user_can_watch_user_log()), code

    severity = Severity.fromstring(form.severity.data)
    severity_changed = cve.severity != severity
    issue_type_changed = cve.issue_type != form.issue_type.data

    cve.issue_type = form.issue_type.data
    cve.description = form.description.data
    cve.severity = severity
    cve.remote = Remote.fromstring(form.remote.data)
    cve.reference = form.reference.data
    cve.notes = form.notes.data

    if severity_changed or issue_type_changed:
        # update cached group severity for all goups containing this issue
        group_ids = [group.id for group in groups]
        issues = (db.session.query(
            CVEGroup, CVE).join(CVEGroupEntry, CVEGroup.issues).join(
                CVE, CVEGroupEntry.cve).group_by(CVEGroup.id).group_by(CVE.id))
        if group_ids:
            issues = issues.filter(CVEGroup.id.in_(group_ids))
        issues = (issues).all()

        if severity_changed:
            group_severity = defaultdict(list)
            for group, issue in issues:
                group_severity[group].append(issue.severity)
            for group, severities in group_severity.items():
                group.severity = highest_severity(severities)

        # update scheduled advisories if the issue type changes
        if advisories and issue_type_changed:
            group_issue_type = defaultdict(set)
            for group, issue in issues:
                group_issue_type[group].add(issue.issue_type)
            for advisory in advisories:
                if Publication.published == advisory.publication:
                    continue
                issue_types = group_issue_type[advisory.group_package.group]
                issue_type = 'multiple issues' if len(
                    issue_types) > 1 else next(iter(issue_types))
                advisory.advisory_type = issue_type

    if db.session.is_modified(cve) or severity_changed or issue_type_changed:
        cve.changed = datetime.utcnow()
        flash('Edited {}'.format(cve.id))

    db.session.commit()
    return redirect('/{}'.format(cve.id))