def delete_proposal(vuln_id: str = None): vulnerability_details = get_vulnerability_details(None, vuln_id, simplify_id=False) vuln = vulnerability_details.get_vulnerability() if not vuln: abort(404) if vuln.state == VulnerabilityState.PUBLISHED: flash_error("Can't delete a published entry w/o reverting it first") return redirect(url_for("profile.view_proposals")) if vuln.state == VulnerabilityState.ARCHIVED: flash_error("Can't delete an archived") return redirect(url_for("profile.view_proposals")) ensure(DELETE, vuln) if (request.method != "GET" and request.form.get("confirm", "false").lower() == "true"): db.session.delete(vuln) db.session.commit() flash("Entry deleted", "success") return redirect(url_for("profile.view_proposals")) return render_template("vulnerability/delete.html", vuln_view=vulnerability_details.vulnerability_view)
def update_proposal(vuln: Vulnerability, form: VulnerabilityDetailsForm): form.populate_obj(vuln) try: new_products = update_products(vuln) except InvalidProducts as ex: flash_error(ex.args[0]) return None with db.session.no_autoflush: changes = vuln.model_changes() # ignore metadata clean_vulnerability_changes(changes) if not changes: flash_error("No changes detected. " "Please modify the entry first to propose a change") return None log.debug("Detected changes: %r", changes) vuln.make_reviewable() db.session.add(vuln) db.session.commit() flash( "Your proposal is in the review queue. " "You can monitor progress in your Proposals Section.", "success", ) return new_products
def edit_proposal(vuln_id: str = None): vulnerability_details = _get_vulnerability_details(None, vuln_id, simplify_id=False) view = vulnerability_details.vulnerability_view vuln = vulnerability_details.get_or_create_vulnerability() ensure(EDIT, vuln) form = VulnerabilityDetailsForm(obj=vuln) # Populate the form data from the vulnerability view if necessary. if form.comment.data == "": form.comment.data = view.comment if request.method == 'POST' and not form.validate(): flash_error("Your proposal contains invalid data, please correct.") form_submitted = form.validate_on_submit() if form_submitted and view.is_creator(): new_products = update_proposal(vuln, form) if new_products is not None: view.products = [(p.vendor, p.product) for p in new_products] return render_template("profile/edit_proposal.html", vulnerability_details=vulnerability_details, form=form)
def add_proposal(vuln: Vulnerability, view: VulnerabilityView, form: VulnerabilityDetailsForm) -> Optional[Vulnerability]: """ Attempts to create a proposal entry which is basically a copy of an existing Vulnerability entry. :param vuln: :param view: :param form: :return: A new Vulnerability copy of the existing entry. """ vuln_clone = vuln.copy() form.populate_obj(vuln_clone) try: update_products(vuln_clone) except InvalidProducts as e: flash_error(e.args[0]) return None with db.session.no_autoflush: changes = vuln.diff(vuln_clone) # ignore metadata changes.pop('date_modified', None) changes.pop('date_created', None) changes.pop('creator', None) changes.pop('state', None) changes.pop('version', None) changes.pop('prev_version', None) changes.pop('reviewer_id', None) changes.pop('reviewer', None) changes.pop('review_feedback', None) changes.pop('id', None) if not changes: flash_error( "No changes detected. Please modify the entry first to propose a change" ) return None logging.debug("Detected changes: %r", changes) vuln_clone.version = None vuln_clone.prev_version = vuln.version vuln_clone.state = VulnerabilityState.READY vuln_clone.creator = g.user # Reset any previous feedback data. vuln_clone.reviewer_id = None vuln_clone.review_feedback = None db.session.add(vuln_clone) db.session.commit() if not vuln_clone.vcdb_id: # TODO: Improve this hack to assign a new vcdb_id here. # Currently, we are just piggy backing on the auto increment of the primary key to ensure uniqueness. # This will likely be prone to race conditions. vuln_clone.vcdb_id = vuln_clone.id db.session.add(vuln_clone) db.session.commit() flash("Your proposal will be reviewed soon.", "success") return vuln_clone
def _can_add_proposal(vuln): existing_user_proposals = Vulnerability.query.filter( Vulnerability.vcdb_id == vuln.vcdb_id, Vulnerability.creator == g.user, Vulnerability.state != VulnerabilityState.PUBLISHED).first() if existing_user_proposals: flash_error( "You already have a pending/unprocessed proposal. Please go to your proposals section." ) return False return True
def _create_vuln_internal(vcdb_id=None): try: vulnerability_details = VulnerabilityDetails(vcdb_id) vulnerability = vulnerability_details.get_or_create_vulnerability() except InvalidIdentifierException as err: return flash_error(str(err), "frontend.serve_index") if vulnerability.id: logging.debug("Preexisting vulnerability entry found: %r", vulnerability.id) delete_form = VulnerabilityDeleteForm() if delete_form.validate_on_submit(): db.session.delete(vulnerability) # Remove the entry. db.session.commit() flash("The entry was deleted.", "success") return redirect("/") form = VulnerabilityDetailsForm(obj=vulnerability) commit = form.data["commits"][0] if not commit["repo_name"]: logging.info("Empty repository name. %r", commit) repo_url = commit["repo_url"] vcs_handler = get_vcs_handler(None, repo_url) if vcs_handler: logging.info("Found name. %r", vcs_handler.repo_name) form.commits[0].repo_name.process_data(vcs_handler.repo_name) if form.validate_on_submit(): try: form.populate_obj(vulnerability) db.session.add(vulnerability) db.session.commit() # TODO: Improve this hack to assign a new vcdb_id here. # Currently, we are just piggy backing on the auto increment # of the primary key to ensure uniqueness. # This will likely be prone to race conditions. vulnerability.vcdb_id = vulnerability.id db.session.add(vulnerability) db.session.commit() logging.debug("Successfully created/updated entry: %r", vulnerability.id) flash("Successfully created/updated entry.", "success") return redirect( url_for("vuln.vuln_view", vcdb_id=vulnerability.vcdb_id)) except InvalidIdentifierException as err: flash_error(str(err)) return render_template( "vulnerability/create.html", vulnerability_details=vulnerability_details, form=form, )
def forbidden_error(err): if g.user is None: return unauthorized_error(err) if cfg.DEBUG and not request.args.get("prod") == "true": return generic_error_page("Forbidden", err), 403 location = request.referrer if not location or not location.startswith(request.url_root): location = "/" flash_error("This action is not allowed") return redirect(location)
def _edit_vuln_internal(vcdb_id: str = None): vulnerability_details = get_vulnerability_details(vcdb_id, simplify_id=False) view = vulnerability_details.vulnerability_view vuln = vulnerability_details.get_or_create_vulnerability() if not _can_add_proposal(vuln): return redirect(url_for("vuln.vuln_view", vcdb_id=vcdb_id)) # Populate the form data from the vulnerability view if necessary. # Updating the vuln instance allows to easier diff the changes. if vuln.comment == "": vuln.comment = view.comment form = VulnerabilityDetailsForm(obj=vuln) form_submitted = form.validate_on_submit() commit = form.data["commits"][0] # TODO: https://github.com/google/vulncode-db/issues/95 - # Add support for non github.com entries long-term again. if commit["commit_link"] and "github.com" not in commit["commit_link"]: flash_error("Entries without a github.com link are currently not supported.") return redirect(url_for("vuln.vuln_view", vcdb_id=vcdb_id)) if form_submitted and commit["commit_link"]: vcs_handler = get_vcs_handler(None, commit["commit_link"]) if not vcs_handler: flash_error("Invalid commit link specified.") return render_template( "vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form, ) logging.info("Found name. %r", vcs_handler.repo_name) form.commits[0].repo_name.process_data(vcs_handler.repo_name) form.commits[0].repo_url.process_data(vcs_handler.repo_url) form.commits[0].commit_hash.process_data(vcs_handler.commit_hash) if form_submitted: proposal_vuln = add_proposal(vuln, form) if proposal_vuln: return redirect( url_for( "vuln.vuln_review", vcdb_id=view.id, vuln_id=proposal_vuln.vcdb_id ) ) with db.session.no_autoflush: return render_template( "vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form, )
def delete_vuln(vcdb_id=None): # TODO implement revert & delete del vcdb_id abort(404) vulnerability_details = get_vulnerability_details(vcdb_id, None, simplify_id=False) vuln = vulnerability_details.get_vulnerability() if not vuln: abort(404) if vuln.state == VulnerabilityState.PUBLISHED: flash_error("Can't delete a published entry w/o reverting it first") return redirect(url_for("vuln.vuln_view")) if vuln.state == VulnerabilityState.ARCHIVED: flash_error("Can't delete an archived") return redirect(url_for("vuln.vuln_view"))
def _can_add_proposal(vuln): # Conditions for creating a proposal: """ - No pending open proposals by the same user. - Proposals can only be made for currently PUBLISHED entries only. """ # TODO: Simplify or move away the query below. existing_user_proposals = Vulnerability.query.filter( or_(Vulnerability.vcdb_id == vuln.vcdb_id, Vulnerability.cve_id == vuln.cve_id), Vulnerability.creator == g.user, Vulnerability.state != VulnerabilityState.PUBLISHED, Vulnerability.state != VulnerabilityState.ARCHIVED).first() if existing_user_proposals: flash_error( "You already have a pending proposal for this entry. Please go to your proposals section." ) return False return True
def view_vuln(vcdb_id, use_template): try: vulnerability_details = VulnerabilityDetails(vcdb_id) vulnerability_details.validate_and_simplify_id() if not vulnerability_details.vulnerability_view: abort(404) except InvalidIdentifierException as err: return flash_error(str(err), "frontend.serve_index") return render_template(use_template, vulnerability_details=vulnerability_details)
def _edit_vuln_internal(vcdb_id: str = None): vulnerability_details = _get_vulnerability_details(vcdb_id, simplify_id=False) view = vulnerability_details.vulnerability_view vuln = vulnerability_details.get_or_create_vulnerability() if not _can_add_proposal(vuln): return redirect(url_for("vuln.vuln_view", vcdb_id=vcdb_id)) # Populate the form data from the vulnerability view if necessary. # Updating the vuln instance allows to easier diff the changes. if vuln.comment == "": vuln.comment = view.comment form = VulnerabilityDetailsForm(obj=vuln) form_submitted = form.validate_on_submit() commit = form.data["commits"][0] if form_submitted and commit["commit_link"]: vcs_handler = get_vcs_handler(None, commit["commit_link"]) if not vcs_handler: flash_error("Invalid commit link specified.") return render_template("vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form) logging.info("Found name. %r", vcs_handler.repo_name) form.commits[0].repo_name.process_data(vcs_handler.repo_name) form.commits[0].repo_url.process_data(vcs_handler.repo_url) form.commits[0].commit_hash.process_data(vcs_handler.commit_hash) if form_submitted: proposal_vuln = add_proposal(vuln, view, form) if proposal_vuln: return redirect( url_for('vuln.vuln_review', vcdb_id=view.id, vuln_id=proposal_vuln.vcdb_id)) with db.session.no_autoflush: return render_template("vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form)
def _edit_vuln_internal(vcdb_id: str = None): vulnerability_details = _get_vulnerability_details(vcdb_id, simplify_id=False) view = vulnerability_details.vulnerability_view vuln = vulnerability_details.get_or_create_vulnerability() if not _can_add_proposal(vuln): return redirect(url_for("vuln.vuln_view", vcdb_id=vcdb_id)) form = VulnerabilityDetailsForm(obj=vuln) # Populate the form data from the vulnerability view if necessary. if form.comment.data == "": form.comment.data = view.comment if form.comment.data == "": form.comment.data = view.comment form_submitted = form.validate_on_submit() commit = form.data["commits"][0] if form_submitted and commit["commit_link"]: vcs_handler = get_vcs_handler(None, commit["commit_link"]) if not vcs_handler: flash_error("Invalid commit link specified.") return render_template("vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form) logging.info("Found name. %r", vcs_handler.repo_name) form.commits[0].repo_name.process_data(vcs_handler.repo_name) form.commits[0].repo_url.process_data(vcs_handler.repo_url) form.commits[0].commit_hash.process_data(vcs_handler.commit_hash) if form_submitted: add_proposal(vuln, form) return redirect(url_for("vuln.vuln_view", vcdb_id=vcdb_id)) return render_template("vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form)
def update_proposal(vuln: Vulnerability, form: VulnerabilityDetailsForm): form.populate_obj(vuln) try: new_products = update_products(vuln) except InvalidProducts as e: flash_error(e.args[0]) return None with db.session.no_autoflush: changes = vuln.model_changes() # ignore metadata changes.pop('date_modified', None) changes.pop('date_created', None) changes.pop('creator', None) changes.pop('state', None) changes.pop('version', None) changes.pop('prev_version', None) changes.pop('reviewer_id', None) changes.pop('reviewer', None) changes.pop('review_feedback', None) changes.pop('id', None) if not changes: flash_error( "No changes detected. Please modify the entry first to propose a change" ) return None log.debug('Detected changes: %r', changes) vuln.make_reviewable() db.session.add(vuln) db.session.commit() flash( "Your proposal is in the review queue. You can monitor progress in your Proposals Section.", "success") return new_products
def _edit_vuln_internal(vcdb_id: str = None): try: vulnerability_details = VulnerabilityDetails(vcdb_id) vulnerability_view = vulnerability_details.vulnerability_view vulnerability = vulnerability_details.get_or_create_vulnerability() except InvalidIdentifierException as err: return flash_error(str(err), "frontend.serve_index") form = VulnerabilityDetailsForm(obj=vulnerability) # Populate the form data from the vulnerability view if necessary. if form.comment.data == "": form.comment.data = vulnerability_view.comment form_submitted = form.validate_on_submit() if form_submitted and _can_add_proposal(vulnerability): add_proposal(vulnerability, form) return render_template("vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form)
def _edit_vuln_internal(vcdb_id: str = None): try: vulnerability_details = VulnerabilityDetails(vcdb_id) vulnerability_view = vulnerability_details.vulnerability_view vulnerability = vulnerability_details.get_or_create_vulnerability() except InvalidIdentifierException as err: return flash_error(str(err), "frontend.serve_index") form = VulnerabilityDetailsForm(obj=vulnerability) # Populate the form data from the vulnerability view if necessary. if form.comment.data == "": form.comment.data = vulnerability_view.comment form_submitted = form.validate_on_submit() if form_submitted and _can_add_proposal(vulnerability): # TODO: This is incomplete/incorrect as the attached relationships such a GitCommit objects get updated. # We have to ensure everything is properly detached and gets copied before any modifications happen. # Currently, this will incorrectly update relationship objects instead of copying them. form.populate_obj(vulnerability) add_proposal(vulnerability) return render_template("vulnerability/edit.html", vulnerability_details=vulnerability_details, form=form)
def vuln_review(vcdb_id, vuln_id): vulnerability_details = get_vulnerability_details(vcdb_id, simplify_id=False) vuln = vulnerability_details.get_or_create_vulnerability() proposal_vulnerability_details = get_vulnerability_details( None, vuln_id=vuln_id, simplify_id=False ) proposal_vuln = proposal_vulnerability_details.get_or_create_vulnerability() ensure(READ, proposal_vuln) form_reject = VulnerabilityProposalReject() form_approve = VulnerabilityProposalApprove() form_assign = VulnerabilityProposalAssign() form_unassign = VulnerabilityProposalUnassign() form_publish = VulnerabilityProposalPublish() if request.method == "POST": if ( request.form["review_response"] == "assign" and form_assign.validate_on_submit() ): ensure(ASSIGN, proposal_vuln) if proposal_vuln.is_reviewable: proposal_vuln.accept_review(g.user) db.session.add(proposal_vuln) db.session.commit() flash("The review was successfully assigned to you.", "success") return redirect(request.url) flash_error("This entry is not in a reviewable state.") if ( request.form["review_response"] == "unassign" and form_unassign.validate_on_submit() ): ensure(ASSIGN, proposal_vuln) if proposal_vuln.is_reviewer(g.user): proposal_vuln.deny_review() db.session.add(proposal_vuln) db.session.commit() flash( "You successfully unassigned yourself from this review.", "success" ) return redirect(request.url) flash_error("This entry is not assigned to you.") if ( request.form["review_response"] == "approve" and form_approve.validate_on_submit() ): ensure(APPROVE, proposal_vuln) proposal_vuln.accept_change() db.session.add(proposal_vuln) db.session.commit() flash( "You approved the proposal. " "Waiting for the entry to be published by an admin.", "success", ) return redirect(request.url) if ( request.form["review_response"] == "reject" and form_reject.validate_on_submit() ): ensure(REJECT, proposal_vuln) proposal_vuln.deny_change(g.user, form_reject.data["review_feedback"]) db.session.add(proposal_vuln) db.session.commit() flash("Waiting for the author to address your feedback.", "success") return redirect(request.url) if ( request.form["review_response"] == "publish" and form_publish.validate_on_submit() ): ensure("PUBLISH", proposal_vuln) proposal_vuln.publish_change() db.session.add(proposal_vuln) db.session.commit() # This might be the first entry of its kind # so no archiving is necessary. if vuln.state: vuln.archive_entry() db.session.add(vuln) db.session.commit() flash("Entry was successfully published.", "success") return redirect(request.url) # Published entries can't be reviewed. # if view.state == VulnerabilityState.PUBLISHED: # raise RequestRedirect("/" + str(vcdb_id)) return render_template( "vulnerability/review/review.html", proposal_vulnerability_details=proposal_vulnerability_details, vulnerability_details=vulnerability_details, form_assign=form_assign, form_unassign=form_unassign, form_reject=form_reject, form_approve=form_approve, form_publish=form_publish, )
def vuln_review(vcdb_id, vuln_id): vulnerability_details = _get_vulnerability_details(vcdb_id, simplify_id=False) view = vulnerability_details.vulnerability_view vuln = vulnerability_details.get_or_create_vulnerability() proposal_vulnerability_details = _get_vulnerability_details( None, vuln_id=vuln_id, simplify_id=False) proposal_view = proposal_vulnerability_details.vulnerability_view proposal_vuln = proposal_vulnerability_details.get_or_create_vulnerability( ) form_reject = VulnerabilityProposalReject() form_approve = VulnerabilityProposalApprove() form_assign = VulnerabilityProposalAssign() form_publish = VulnerabilityProposalPublish() if request.method == 'POST': # TODO: Add proper ACL changes to all actions here. if request.form[ "review_response"] == "assign" and form_assign.validate_on_submit( ): ensure('ASSIGN', proposal_vuln) if proposal_vuln.is_reviewable(): proposal_vuln.accept_review(g.user) db.session.add(proposal_vuln) db.session.commit() flash("The review was successfully assigned to you.", "success") return redirect(request.url) else: flash_error("This entry is not in a reviewable state.") if request.form[ "review_response"] == "approve" and form_approve.validate_on_submit( ): ensure('APRROVE', proposal_vuln) proposal_vuln.accept_change() db.session.add(proposal_vuln) db.session.commit() flash( "You approved the proposal. Waiting for the entry to be published by an admin.", "success") return redirect(request.url) if request.form[ "review_response"] == "reject" and form_reject.validate_on_submit( ): ensure('REJECT', proposal_vuln) proposal_vuln.deny_change(form_reject.data["review_feedback"]) db.session.add(proposal_vuln) db.session.commit() flash("Waiting for the author to address your feedback.", "success") return redirect(request.url) if request.form[ "review_response"] == "publish" and form_publish.validate_on_submit( ): ensure('PUBLISH', proposal_vuln) proposal_vuln.publish_change() db.session.add(proposal_vuln) vuln.archive_entry() db.session.add(vuln) db.session.commit() flash("Entry was successfully published.", "success") return redirect(request.url) # Published entries can't be reviewed. # if view.state == VulnerabilityState.PUBLISHED: # raise RequestRedirect("/" + str(vcdb_id)) return render_template( "vulnerability/review.html", proposal_vulnerability_details=proposal_vulnerability_details, vulnerability_details=vulnerability_details, form_assign=form_assign, form_reject=form_reject, form_approve=form_approve, form_publish=form_publish)