代码拉取完成,页面将自动刷新
package controllers
import (
"bytes"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"net/http"
"os"
"regexp"
"strconv"
"strings"
"sync"
"time"
"github.com/opensourceways/go-gitee/gitee"
"github.com/opensourceways/server-common-lib/utils"
"k8s.io/apimachinery/pkg/util/sets"
"cvevulner/common"
"cvevulner/cve-timed-task/tabletask"
"cvevulner/models"
"cvevulner/task"
"cvevulner/taskhandler"
"cvevulner/util"
"github.com/astaxie/beego"
"github.com/astaxie/beego/logs"
)
var (
//GiteeUserAgent gitee hook request flag
GiteeUserAgent = "git-oschina-hook"
//XGiteeToken password or sign
XGiteeToken = "X-Gitee-Token"
//XGIteeEventType webhook event type
XGIteeEventType = "X-Gitee-Event"
//NoteHookType type of comment
NoteHookType = "Note Hook"
//PullReqHookType type of pull request
PullReqHookType = "Merge Request Hook"
//PushTagHookType type of push or tag
PushTagHookType = "Tag Push Hook"
//IssueHookType type of issue
IssueHookType = "Issue Hook"
)
const (
//CommentAnalysisCplTpl complete comment analysis reply template
CommentAnalysisCplTpl = "@%v %v"
//ReviewPrivateLettersTpl send private review letters template
ReviewPrivateLettersTpl = `%s(%s)analysis is over,CVEScore:%v;OpenEulerScore:%v.Please review!`
//ReviewRejectScore reply the review reject template
ReviewRejectScore = `@%v you submit issue score audit failed(reject by %v),Please re-analyze and submit!`
//ReviewApproveScore replay the review approve template
ReviewApproveScore = `@%v you submit issue score audit success(approved by %v),You can proceed to the next step!`
//CommentReviewTpl comment review template
CommentReviewTpl = `%v The CVE score needs to be reviewed (the review instruction /approve or /reject means agreement and rejection).`
//IssueRejectState issue state rejected
IssueRejectState = "rejected"
//IssueCloseState issue state closed
IssueCloseState = "closed"
//IssueProgressState issue state progressing
IssueProgressState = "progressing"
//IssueOpenState issue state open
IssueOpenState = "open"
//AnalysisComplete issue analysis complete comment
AnalysisComplete = "@%v 经过 cve-manager 解析, 已分析的内容如下表所示:\n"
// Content review tips
ContentReview = "%v 请完成以下操作:\n"
// Not filling in the correct format
IssueErroFormat = "%v 经过 cve-manager 解析, 填写openEuler评分未通过安全组成员审核需要再次在评论区提交评分,通过审核后才能关闭ISSUE."
// Remind the security group to review
CommentReviewRemind = "%v 经过 cve-manager 解析 openEuler评分 已改变 需要您及时进行审核,以便maintainer进行后续操作."
// Review private messages
CommentPrivateReview = "%v 仓库的CVE和安全问题的ISSUE,需要您进行审核,CVE编号: %v"
// Rating review failed
CommentPrivateOpenEuler = "%v 仓库的CVE和安全问题的ISSUE, CVE编号: %v, 填写openEuler评分未通过安全组成员审核需要再次在评论区提交评分,通过审核后才能关闭ISSUE."
// Review reminder
CommentReviewRemindMaintainer = "@%v 经过 cve-manager 解析 openEuler评分 已改变 需要等待安全组成员审核通过以后, 才能进行后续操作."
CIssueType = "CVE和安全问题"
HasCreateIssue = "@%v %v 在当前软件仓下已经创建过对应的[ISSUE](%s), 请不要重复创建"
CreateIssueReject = "@%v %v 在当前软件仓下已经创建过对应的[ISSUE](%s), 请不要重复创建, 当前ISSUE将被工具设置为已拒绝."
createIssueRepeat = "/reason 重复创建"
CommentCheckVersion = "@%v 请确认分支: %v 受影响/不受影响."
// rejected or upend
CommentRejectedState = `@%v 当前issue状态为: %v,请先修改issue状态, 否则评论无法被识别.`
// Get cve information comment
CommentGetNvdCveSuccess = `@%v CVE信息从NVD同步成功, 稍后请重新加载页面.`
CommentGetNvdCveFailed = `@%v CVE信息从NVD同步失败, 请稍后重试, 或者数据源不存在.`
CommentRepeatIssue = `%v 请检查当前: %v,是否重复创建, issue编号: %v, 重复创建的issue,将不会被再次识别.`
webhookCommentLogTag = "webhook-comment"
cmdCheckIssue = "/check-issue"
reasonCommand = "/reason"
reasonX = "xxxxxx"
openEulerBotName = "openeuler-ci-bot"
majunBotName = "openLiBing-bot"
)
var comLock sync.Mutex
// HookEventControllers gitee hook callback
type HookEventControllers struct {
beego.Controller
}
type AgencyPrams struct {
CveUrl string
PatchUrl string
}
// Post handle gitee webhook
// @router / [post]
func (c *HookEventControllers) Post() {
if ok := c.isLegitimateHookEvent(); !ok {
logs.Error(webhookCommentLogTag, "isLegitimateHookEvent", c.Ctx.Input.RequestBody)
c.Ctx.ResponseWriter.WriteHeader(406)
c.Ctx.WriteString("Illegal incident, discarded")
return
}
eventType := c.Ctx.Request.Header.Get(XGIteeEventType)
c.Ctx.ResponseWriter.WriteHeader(200)
c.Ctx.WriteString("Event received: " + eventType)
switch eventType {
case NoteHookType: //handle comment hook data
c.handleNoteDate()
case PullReqHookType:
c.handlePullReq()
case IssueHookType:
c.handleIssue()
case PushTagHookType:
c.handlePushTag()
default:
logs.Info(eventType)
}
}
// isLegitimateHookEvent according to gitee doc judge
func (c *HookEventControllers) isLegitimateHookEvent() (ok bool) {
ok = true
//judge user agent
uAgent := c.Ctx.Request.Header.Get("User-Agent")
if uAgent != GiteeUserAgent {
ok = false
}
ctType := c.Ctx.Request.Header.Get("Content-Type")
if "application/json" != ctType {
ok = false
}
//judge hook password
xToken := c.Ctx.Request.Header.Get(XGiteeToken)
//logs.Info(xToken)
hookPwd := beego.AppConfig.String("hook::hookpwd")
if xToken != hookPwd {
logs.Error("hookPwd Err, xToken: ", xToken)
}
return
}
func (c *HookEventControllers) handleNoteDate() {
var hookNote models.CommentPayload
err := json.Unmarshal(c.Ctx.Input.RequestBody, &hookNote)
if err != nil {
logs.Error(webhookCommentLogTag, "unmarshal payload failed:", err)
return
}
hookPwd := beego.AppConfig.String("hook::hookpwd")
hookNote.Password = util.TrimString(hookNote.Password)
hookPwd = util.TrimString(hookPwd)
if hookNote.Action == "comment" && hookNote.NoteableType == "Issue" && hookNote.Password == hookPwd {
logs.Info(string(c.Ctx.Input.RequestBody))
//handle issue comment
go handleIssueComment(hookNote)
}
}
func (c *HookEventControllers) handlePullReq() {
}
func (c *HookEventControllers) handlePushTag() {
}
func (c *HookEventControllers) handleIssue() {
logs.Error("issue event payload: ", string(c.Ctx.Input.RequestBody))
issueHook := models.IssuePayload{}
err := json.Unmarshal(c.Ctx.Input.RequestBody, &issueHook)
if err != nil {
logs.Error(err, "\n, RequestBody: ", string(c.Ctx.Input.RequestBody))
return
}
cuAccount := issueHook.Sender.Login
if issueHook.Issue.Number == "" || cuAccount == "" {
logs.Error("Data has null values: issueNum, cuAccount: ", issueHook.Issue.Number, cuAccount)
return
}
nameSpace := util.TrimString(issueHook.Repository.NameSpace)
organizationID := int8(1)
organizationID = taskhandler.GetOrganizationId(nameSpace)
if organizationID != 1 {
return
}
hookPwd := beego.AppConfig.String("hook::hookpwd")
issueHook.Password = util.TrimString(issueHook.Password)
if issueHook.Password != hookPwd {
logs.Error("Hook callback pwd verification error, hook: ", issueHook)
return
}
if issueHook.Action == "assign" {
//Update the person in charge of the issue template
issueTmp := models.IssueTemplate{IssueNum: issueHook.Iid, IssueId: issueHook.Issue.Id}
err := models.GetIssueTemplateByColName(&issueTmp, "issue_num", "issue_id")
if err != nil {
logs.Error(err, ",issueTmp: ", issueTmp)
return
}
issueTmp.Assignee = issueHook.Assignee.Login
err = models.UpdateIssueTemplate(&issueTmp, "issue_assignee")
if err != nil {
logs.Error(err, ",issueTmp: ", issueTmp)
}
}
if issueHook.Action == "state_change" {
//handle issue state change
err = handleIssueStateChange(&issueHook)
if err != nil {
logs.Error(err, "\n,", issueHook)
return
}
}
if issueHook.Action == "open" {
if issueHook.User.Login == openEulerBotName || issueHook.User.Login == majunBotName {
return
}
owner, token := common.GetOwnerAndToken("", organizationID)
issueTmp := models.IssueTemplate{IssueNum: issueHook.Iid, IssueId: issueHook.Issue.Id}
err = models.GetIssueTemplateByColName(&issueTmp, "issue_num", "issue_id")
if err == nil && issueTmp.TemplateId > 0 {
vc := models.VulnCenter{CveId: issueTmp.CveId}
vcErr := models.GetVulnCenterByCid(&vc, "CveId")
if vcErr == nil && vc.CveId > 0 && vc.OrganizationID == organizationID {
//cc := fmt.Sprintf(CommentRepeatIssue, "@"+cuAccount, issueTmp.CveNum, issueTmp.IssueNum)
//taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
logs.Error("Duplicate webhook data is discarded directly, cveNum:", vc.CveNum, ",packName: ", vc.RepoName)
return
}
}
err = gitAddIssueProc(&issueHook, organizationID)
if err != nil {
logs.Error(err)
}
issueTmp = models.IssueTemplate{IssueNum: issueHook.Iid, IssueId: issueHook.Issue.Id}
err = models.GetIssueTemplateByColName(&issueTmp, "issue_num", "issue_id")
if err == nil {
PostTriggerGetCve(issueTmp, owner, token, cuAccount)
}
return
}
if issueHook.Action == "delete" {
err = gitDelIssueProc(&issueHook, organizationID)
if err != nil {
logs.Error(err, "\n, ", issueHook)
return
}
}
}
func closeIssuePrivilage(issueHook *models.IssuePayload, issueTmp *models.IssueTemplate,
token, owner, fixed, unFix string, cveCenter *models.VulnCenter) bool {
closePrBool := true
if issueHook.Sender.UserName != "" && len(issueHook.Sender.UserName) > 1 {
if isReviewer(util.TrimString(issueHook.Sender.UserName)) {
if msg, tb, ok := taskhandler.CheckIssueClosedAnalysisComplete(issueTmp); !ok {
//send comment to issue
issueTmp.IssueStatus = 1
issueTmp.MtAuditFlag = 1
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
*cveCenter, *issueTmp)
if issueErr == nil {
na := "\n**请确认分析内容的准确性,待分析内容请填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(ContentReview, "@"+issueHook.Sender.UserName) + tb + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
content := fmt.Sprintf("%v 仓库的CVE和安全问题的ISSUE,CVE编号: %v,", issueTmp.Repo, issueTmp.CveNum)
taskhandler.SendPrivateLetters(token, content+msg, issueHook.Issue.Assignee.Login)
}
} else {
issueTmp.StatusName = issueHook.Issue.StateName
issueTmp.SaAuditFlag = 1
issueTmp.MtAuditFlag = 1
issueTmp.OpAuditFlag = 1
issueTmp.Status = 3
if isNormalCloseIssue(issueTmp.CveId, issueTmp.IssueStatus) {
issueTmp.IssueStatus = 2
cveCenter.IsExport = 3
issueTmp.IssueLabel = fixed
} else {
issueTmp.IssueStatus = 6
cveCenter.IsExport = 2
issueTmp.IssueLabel = unFix
}
}
closePrBool = false
}
}
return closePrBool
}
func sigReviewSend(issueHook *models.IssuePayload, issueTmp *models.IssueTemplate,
token, owner, fixed, unFix, assignee string, cveCenter *models.VulnCenter) {
issueTmp.IssueStatus = 1
issueTmp.Status = 1
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
*cveCenter, *issueTmp)
if issueErr == nil {
if issueTmp.OpAuditFlag == 2 {
cc := fmt.Sprintf(IssueErroFormat, assignee)
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
content := fmt.Sprintf(CommentPrivateOpenEuler, issueTmp.Repo, issueTmp.CveNum)
taskhandler.SendPrivateLetters(token, content, issueHook.Sender.UserName)
} else if issueTmp.OpAuditFlag == 0 {
list, revErr := models.GetSecurityReviewerList()
if revErr == nil && len(list) > 0 {
content := fmt.Sprintf(CommentPrivateReview, issueTmp.Repo, issueTmp.CveNum)
ns := make([]string, len(list))
for k, v := range list {
ns[k] = "@" + v.NameSpace + " "
taskhandler.SendPrivateLetters(token, content, v.NameSpace)
}
if len(ns) > 0 {
cc := fmt.Sprintf(CommentReviewRemind, strings.Join(ns, ","))
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
}
} else {
logs.Error("revErr: ", revErr)
}
}
}
}
func getMaintainer(path, prSender, assignee string) string {
maintainerList, mainOk := models.QueryRepoAllMaintainer(path)
assList := []string{}
if mainOk && len(maintainerList) > 0 {
for _, v := range maintainerList {
assList = append(assList, "@"+v.MemberName+" ")
}
}
if prSender != "" && len(prSender) > 1 {
isListBool := common.IsValueInList("@"+prSender+" ", assList)
if !isListBool {
assList = append(assList, "@"+prSender+" ")
}
}
maintainerVaule := ""
if len(assList) > 0 {
maintainerVaule = strings.Join(assList, ",")
} else {
maintainerVaule = "@" + assignee + " "
}
return maintainerVaule
}
func otherCloseIssueProc(issueHook *models.IssuePayload, issueTmp *models.IssueTemplate,
token, owner, fixed, unFix, path string, cveCenter *models.VulnCenter) {
unFixList := taskhandler.CheckAffectVerComplete(issueTmp.AffectedVersion, issueTmp.Repo,
issueTmp.OwnedVersion, cveCenter.OrganizationID)
if len(unFixList) > 0 {
//send comment to issue
issueTmp.IssueStatus = 1
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
*cveCenter, *issueTmp)
if issueErr == nil {
na := "\n**请确认分支信息是否填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(CommentCheckVersion, issueHook.Sender.UserName, strings.Join(unFixList, ",")) + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
content := fmt.Sprintf("%v 仓库的CVE和安全问题的ISSUE,CVE编号: %v,", issueTmp.Repo, issueTmp.CveNum)
taskhandler.SendPrivateLetters(token, content, issueHook.Sender.UserName)
}
} else {
commonFunc := taskhandler.CheckOtherIssueClosedAnalysisComplete
if _, tb, ok := commonFunc(issueTmp, cveCenter); !ok {
//send comment to issue
issueTmp.IssueStatus = 1
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, path,
*cveCenter, *issueTmp)
if issueErr == nil {
na := "\n**请确认分析内容的准确性,待分析内容请填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(ContentReview, "@"+issueHook.Sender.UserName) + tb + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, path, token)
}
} else {
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
issueTmp.Status = 1
assignee := "@" + issueTmp.Assignee
issuePrFlag := VerifyIssueAsPr(issueTmp, *cveCenter, false,
assignee, issueHook.Sender.UserName)
if issuePrFlag {
//1. change issue status
issueTmp.IssueStatus = 2
cveCenter.IsExport = 3
issueTmp.StatusName = issueHook.Issue.StateName
issueTmp.Status = 3
if isNormalCloseIssue(issueTmp.CveId, issueTmp.IssueStatus) {
issueTmp.IssueStatus = 2
cveCenter.IsExport = 3
issueTmp.IssueLabel = fixed
} else {
issueTmp.IssueStatus = 6
cveCenter.IsExport = 2
issueTmp.IssueLabel = unFix
}
} else {
issueTmp.IssueStatus = 1
cveCenter.IsExport = 0
}
}
}
}
func closeIssueProc(issueHook *models.IssuePayload, issueTmp *models.IssueTemplate,
token, owner, fixed, unFix string, cveCenter *models.VulnCenter) {
closePrBool := true
closeIssuePrFlag, closeOk := beego.AppConfig.Int64("cve::close_issue_privilege")
if closeOk == nil && closeIssuePrFlag == 1 {
closePrBool = closeIssuePrivilage(issueHook, issueTmp,
token, owner, fixed, unFix, cveCenter)
}
if closePrBool {
issueTmp.Status = 1
cveCenter.IsExport = 0
issueTmp.MtAuditFlag = 1
assignee := getMaintainer(issueTmp.Repo, issueHook.Sender.UserName, issueTmp.Assignee)
openScoreFlag := true
if issueTmp.OpenEulerScore != issueTmp.NVDScore && issueTmp.NVDScore > 0 &&
issueTmp.OpAuditFlag != 1 && issueTmp.OpenEulerScore > 0 {
//send comment to issue
openScoreFlag = false
sigReviewSend(issueHook, issueTmp, token, owner, fixed, unFix, assignee, cveCenter)
}
if openScoreFlag {
if msg, tb, ok := taskhandler.CheckIssueAnalysisComplete(issueTmp); !ok {
//send comment to issue
issueTmp.IssueStatus = 1
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
*cveCenter, *issueTmp)
if issueErr == nil {
na := "\n**请确认分析内容的准确性,待分析内容请填写完整,否则将无法关闭当前issue.**"
//cc := fmt.Sprintf(ContentReview, "@"+issueHook.Sender.UserName) + tb + na
cc := fmt.Sprintf(CommentAnalysisCplTpl, issueHook.Sender.UserName, msg) + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
content := fmt.Sprintf("%v 仓库的CVE和安全问题的ISSUE,CVE编号: %v,", issueTmp.Repo, issueTmp.CveNum)
taskhandler.SendPrivateLetters(token, content+msg, issueHook.Sender.UserName)
}
} else {
//1. change issue status
issueTmp.IssueStatus = 2
//issueTmp.Status = 3
cveCenter.IsExport = 3
if issueTmp.MtAuditFlag == 0 {
issueTmp.IssueStatus = 1
issueTmp.Status = 1
cveCenter.IsExport = 0
issueTmp.IssueLabel = unFix
issueTmp.StatusName = "open"
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
*cveCenter, *issueTmp)
if issueErr == nil {
na := "\n**issue关闭前,请确认模板分析内容的准确性与完整性,确认无误后,请在评论区输入: /approve, 否则无法关闭当前issue.**"
cc := fmt.Sprintf(ContentReview, assignee) + tb + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
}
return
}
if err := SetIssueStateByReason(issueTmp, issueHook.Issue.StateName); err != nil {
logs.Error("SetIssueStateByReason of failed:", issueTmp.IssueNum, err)
}
if issueTmp.IsIssueComplete() {
issueTmp.ResetLabel()
}
}
}
}
}
// Entry function for handling issue status
func handleIssueStateChange(issueHook *models.IssuePayload) error {
unFix := beego.AppConfig.String("labelUnFix")
fixed := beego.AppConfig.String("labelFixed")
uNaffected := beego.AppConfig.String("labeUnaffected")
issueId := issueHook.Issue.Id
issueTmp := models.IssueTemplate{}
issueTmp.IssueId = issueId
issueTmp.IssueNum = issueHook.Iid
repoPath := ""
if issueHook.Issue.Repository.Path != "" &&
len(issueHook.Issue.Repository.Path) > 1 {
repoPath = issueHook.Issue.Repository.Path
}
issueErr := models.GetIssueTemplateByColName(&issueTmp, "issue_num", "issue_id")
if issueErr != nil {
return issueErr
}
path := issueTmp.Repo
cveCenter := models.VulnCenter{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum}
cveErr := models.GetVulnCenterByCid(&cveCenter, "cve_id", "cve_num")
if cveErr != nil {
return cveErr
}
if len(repoPath) > 1 && repoPath != path {
cveCenter.PackName = repoPath
cveCenter.RepoName = issueTmp.OwnedComponent
models.UpdateVulnCenter(&cveCenter, "PackName", "RepoName")
issueTmp.Repo = repoPath
models.UpdateIssueTemplate(&issueTmp, "Repo")
path = repoPath
}
if issueHook.Issue.StateName == "已挂起" {
logs.Error("The current issue has been suspended and will not be processed, issueHook: ", issueHook)
issueTmp.Status = 5
issueTmp.StatusName = "suspended"
models.UpdateIssueTemplate(&issueTmp, "Status", "StatusName")
return errors.New("The current issue has been suspended and will not be processed")
}
if issueHook.Issue.StateName == "已拒绝" {
logs.Error("The current issue has been rejected and will not be processed, issueHook: ", issueHook)
issueTmp.Status = 4
issueTmp.StatusName = "rejected"
issueTmp.RejectReason = getRejectReason(issueTmp.Owner, issueTmp.Repo, issueTmp.IssueNum)
models.UpdateIssueTemplate(&issueTmp, "Status", "StatusName", "reject_reason")
return errors.New("The current issue has been rejected and will not be processed")
}
owner, token := common.GetOwnerAndToken(cveCenter.CveNum, cveCenter.OrganizationID)
issueTmp.StatusName = issueHook.Issue.StateName
if cve, ok := models.QueryIssueCveByNum(cveCenter.CveNum, cveCenter.PackName, cveCenter.OrganizationID); ok {
cve.State = issueHook.State
cve.IssueState = issueHook.Issue.StateName
if err := cve.InsertOrUpdate(1); err != nil {
logs.Error("update GiteOriginIssue failed, err:", err)
}
}
switch issueHook.State {
case IssueOpenState:
issueTmp.Status = 1
cveCenter.IsExport = 0
_, _, ok := taskhandler.CheckIssueAnalysisComplete(&issueTmp)
if ok {
issueTmp.IssueStatus = 3
} else {
issueTmp.IssueStatus = 1
}
issueTmp.IssueLabel = unFix
//issueTmp.IssueStatus = 1
case IssueProgressState:
issueTmp.Status = 2
cveCenter.IsExport = 0
_, _, ok := taskhandler.CheckIssueAnalysisComplete(&issueTmp)
if ok {
issueTmp.IssueStatus = 3
} else {
issueTmp.IssueStatus = 1
}
issueTmp.IssueLabel = unFix
case IssueCloseState:
if issueTmp.Status == 3 {
// The issue has been closed and cannot be operated again
logs.Error("The issue has been closed and cannot be operated again,issuetmp: ", issueTmp)
return errors.New("The issue has been closed and cannot be operated again")
}
if cveCenter.OrganizationID == 1 {
closeIssueProc(issueHook, &issueTmp, token, owner, fixed, unFix, &cveCenter)
} else {
otherCloseIssueProc(issueHook, &issueTmp, token, owner, fixed, unFix, path, &cveCenter)
}
case IssueRejectState:
issueTmp.Status = 4
issueTmp.IssueStatus = 6
cveCenter.IsExport = 2
issueTmp.IssueLabel = uNaffected
}
updateBool := updateTempAndCenter(issueTmp, cveCenter, token, owner)
if !updateBool {
return errors.New("handle issue state hook appear error maybe some step fail")
}
return nil
}
func getRejectReason(owner, repo, issueNum string) string {
comments, err := common.GetOpenEulerGiteeClient().ListIssueComments(owner, repo, issueNum)
if err != nil {
logs.Error("get issue comments failed:", err.Error())
return ""
}
var reason string
for i := len(comments) - 1; i >= 0; i-- {
body := comments[i].Body
index := strings.Index(body, reasonCommand)
if index == -1 {
continue
}
reason = strings.TrimSpace(body[index+len(reasonCommand):])
if reason == reasonX {
continue
}
break
}
return util.TrimStringNR(reason)
}
// When the issue status is complete, verify whether the pr is associated
func VerifyIssueAsPr(issueTmp *models.IssueTemplate, cveCenter models.VulnCenter,
effectFlag bool, assignee, prSend string) bool {
sn := models.SecurityNotice{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum}
secErr := sn.Read("cve_id", "cve_num")
if secErr != nil {
logs.Error("no data has been found, issueTmp: ", issueTmp)
return true
}
affectBranchsxList := make([]string, 0)
affectedBranchs := ""
path := cveCenter.PackName
if cveCenter.OrganizationID == 3 {
if strings.EqualFold(path, "mindarmour") || strings.EqualFold(path, "mindinsight") {
return true
}
}
affectProductList := make([]string, 0)
tmpAffectBranchsxList := make([]string, 0)
owner, token := common.GetOwnerAndToken(cveCenter.CveNum, cveCenter.OrganizationID)
if cveCenter.OrganizationID == 4 {
affectedBranchs = beego.AppConfig.String("openlookeng::openlookeng_version")
affectBranchsxList = taskhandler.CreateBrandAndTags(token, owner, path, cveCenter.OrganizationID)
} else if cveCenter.OrganizationID == 3 {
affectedBranchs = beego.AppConfig.String("mindspore::mindspore_version")
affectBranchsxList = taskhandler.CreateBrandAndTags(token, owner, path, cveCenter.OrganizationID)
} else if cveCenter.OrganizationID == 2 {
affectedBranchs = strings.ReplaceAll(sn.AffectProduct, "/", ",")
if len(path) < 2 {
path = beego.AppConfig.String("opengauss::gauss_issue_path")
}
affectBranchsxList, _ = taskhandler.GetBranchesInfo(token, owner, path, cveCenter.OrganizationID)
} else {
affectedBranchs = beego.AppConfig.String("cve::affected_branchs")
path = issueTmp.Repo
if affectedBranchs != "" && len(affectedBranchs) > 0 {
affectBranchsxList = strings.Split(affectedBranchs, ",")
}
}
if sn.WillFixProduct != "" && len(sn.WillFixProduct) > 1 {
tmpAffectBranchsxList = strings.Split(sn.WillFixProduct, "/")
}
affectProductList = common.RemoveDupString(tmpAffectBranchsxList)
if len(affectProductList) > 0 {
issueTmp.SaAuditFlag = 0
branchMaps := make(map[string]bool)
for _, brands := range affectProductList {
brands = common.BranchVersionRep(brands)
if len(affectBranchsxList) > 0 {
keyBandList := []string{}
for _, affectBranch := range affectBranchsxList {
affectBranch = common.BranchVersionRep(affectBranch)
if affectBranch == brands {
// Query the repo that needs to submit an issue
if cveCenter.OrganizationID == 3 {
mdbt := models.MindSporeBrandTags{PackageName: path, Tags: affectBranch}
mtErr := models.QueryMindSporeBrandTags(&mdbt, "PackageName", "Tags")
if mtErr == nil {
keyBandList = append(keyBandList, mdbt.Brand)
}
} else if cveCenter.OrganizationID == 4 {
keyBandList = append(keyBandList, "master")
} else {
keyBandList = append(keyBandList, affectBranch)
}
}
}
if len(keyBandList) > 0 {
prList := getRepoIssueAllPR(keyBandList, token, owner, path, *issueTmp)
for _, brh := range keyBandList {
if len(prList) == 0 {
branchMaps[brh] = false
} else {
for _, prl := range prList {
if brh == prl.Branch {
branchMaps[brh] = prl.BrFlag
break
}
}
}
if _, ok := branchMaps[brh]; !ok {
branchMaps[brh] = false
}
}
}
}
}
if len(branchMaps) == 0 {
logs.Info("sn.WillFixProduct: ", sn.WillFixProduct, ",There is no branch to follow to associate with pr")
return true
}
brandStr := ""
//logs.Info("branchMaps===> ", branchMaps)
for brand, bv := range branchMaps {
if !bv {
logs.Error("brand: ", brand, ", pr is not related to issue, issueTmp: ", issueTmp)
brandStr = brandStr + brand + "/"
}
}
if brandStr != "" && len(brandStr) > 1 {
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, path,
cveCenter, *issueTmp)
if issueErr == nil {
commentBody := assignee + "\n" +
"关闭issue前,需要将受影响的分支在合并pr时关联上当前issue编号: #" + issueTmp.IssueNum + "\n" +
"受影响分支: " + brandStr[:len(brandStr)-1] + "\n" +
"具体操作参考: " + "https://gitee.com/help/articles/4142" + "\n"
taskhandler.AddCommentToIssue(commentBody, issueTmp.IssueNum, owner, path, token)
content := issueTmp.Repo + " 仓库的CVE和安全问题的ISSUE,CVE编号: " + issueTmp.CveNum +
",关闭issue前,需要将受影响的分支在合并pr时关联上当前issue编号: #" + issueTmp.IssueNum +
",受影响分支: " + brandStr[:len(brandStr)-1] +
",具体操作参考: " + "https://gitee.com/help/articles/4142."
taskhandler.SendPrivateLetters(token, content, prSend)
}
return false
}
} else {
if effectFlag && issueTmp.SaAuditFlag == 0 {
unaffectedBranchList := []string{}
if issueTmp.AffectedVersion != "" && len(issueTmp.AffectedVersion) > 1 {
unaffectedBranchList = paraAffectBrands(issueTmp.AffectedVersion)
}
branchStrs := ""
if len(unaffectedBranchList) > 0 {
for _, brands := range unaffectedBranchList {
if len(affectBranchsxList) > 0 {
for _, affectBranch := range affectBranchsxList {
if affectBranch == brands {
branchStrs = branchStrs + brands + "/"
}
}
}
}
}
if branchStrs != "" && len(branchStrs) > 1 {
branchStrs = branchStrs[:len(branchStrs)-1]
list, err := models.GetSecurityReviewerList()
if err != nil {
logs.Error("err: ", err, "\n, issueTmp: ", issueTmp)
issueTmp.SaAuditFlag = 1
return true
}
if len(list) == 0 {
logs.Error("list is null, issueTemp: ", issueTmp)
issueTmp.SaAuditFlag = 1
return true
}
anName := []string{}
content := fmt.Sprintf(CommentPrivateReview, issueTmp.Repo, issueTmp.CveNum)
for _, v := range list {
anName = append(anName, "@"+v.NameSpace+" ")
taskhandler.SendPrivateLetters(token, content, v.NameSpace)
}
if len(anName) > 0 {
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
cveCenter, *issueTmp)
if issueErr == nil {
assignee := strings.Join(anName, ",")
commentBody := assignee + "\n" +
"关闭issue前,请确认分支: " + branchStrs + ": 受影响/不受影响, 如受影响,请联系maintainer: @" +
issueTmp.Assignee + ", **进行处理后, 或者按照模板格式在评论区填写内容, 最后记得在评论区回复: /approve ,才能正常关闭issue.**"
taskhandler.AddCommentToIssue(commentBody, issueTmp.IssueNum, owner, issueTmp.Repo, token)
}
return false
} else {
issueTmp.SaAuditFlag = 1
}
} else {
issueTmp.SaAuditFlag = 1
}
}
}
return true
}
func paraAffectBrands(affectedVersion string) (unaffectedBranchList []string) {
brandsGroup := strings.Split(affectedVersion, ",")
if len(brandsGroup) > 0 {
for _, brand := range brandsGroup {
if brand == "" || len(brand) < 2 {
continue
}
brand = common.BranchVersionRep(brand)
brandList := strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if prams != "受影响" {
unaffectedBranchList = append(unaffectedBranchList, brandList[0])
}
} else {
brandList = strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if prams != "受影响" {
unaffectedBranchList = append(unaffectedBranchList, brandList[0])
}
}
}
if len(brandList) == 1 {
unaffectedBranchList = append(unaffectedBranchList, brandList[0])
}
}
}
return unaffectedBranchList
}
func paraAffectBrandBool(affectedVersion string) bool {
unaffectedBranchList := make([]string, 0)
brandsGroup := strings.Split(affectedVersion, ",")
if len(brandsGroup) > 0 {
for _, brand := range brandsGroup {
if brand == "" || len(brand) < 2 {
continue
}
brand = common.BranchVersionRep(brand)
brandList := strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if prams == "受影响" || prams == "不受影响" {
unaffectedBranchList = append(unaffectedBranchList, brandList[0])
}
} else {
brandList = strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if prams == "受影响" || prams == "不受影响" {
unaffectedBranchList = append(unaffectedBranchList, brandList[0])
}
}
}
}
}
if len(unaffectedBranchList) > 0 {
return true
}
return false
}
func paraAnalysisBrandBool(analysisVersion string) bool {
unaffectedBranchList := make([]string, 0)
analysisVersion = strings.ReplaceAll(analysisVersion, ":", ":")
brandsGroup := strings.Split(analysisVersion, ",")
if len(brandsGroup) > 0 {
for _, brand := range brandsGroup {
if brand == "" || len(brand) < 2 {
continue
}
brand = common.BranchVersionRep(brand)
brandList := strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if common.AnalysisSets.Has(prams) {
unaffectedBranchList = append(unaffectedBranchList, brandList[0])
}
}
}
}
return len(unaffectedBranchList) > 0
}
func getPRRelatedBrandsAllIssue(token, owner, repo string, num int, issueNum string) bool {
issueFlag := false
url := fmt.Sprintf(`https://gitee.com/api/v5/repos/%s/%s/pulls/%v/issues`, owner, repo, num)
pageSize := 20
pageCount := 1
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
logs.Error(err)
return false
}
q := req.URL.Query()
q.Add("access_token", token)
q.Add("per_page", strconv.Itoa(pageSize))
for {
q.Del("page")
q.Add("page", strconv.Itoa(pageCount))
req.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(req)
if err != nil {
logs.Error(err)
break
}
if resp.StatusCode == http.StatusOK {
var il []models.HookIssue
read, err := ioutil.ReadAll(resp.Body)
resp.Body.Close()
if err != nil {
logs.Error(err)
break
}
err = json.Unmarshal(read, &il)
if err != nil {
logs.Error(err)
break
}
for _, v := range il {
d, ok := isLegallyIssue(v)
if ok {
if issueNum == d.Number {
issueFlag = true
break
}
}
}
if len(il) < pageSize {
break
}
pageCount++
} else {
resp.Body.Close()
break
}
}
return issueFlag
}
// Verify that the current issue meets the requirements
func isLegallyIssue(i models.HookIssue) (pri models.PullRequestIssue, ok bool) {
if i.IssueType != CIssueType || (i.State != "closed" && i.State != "已完成") {
return
}
tt := strings.Trim(i.Title, " ")
regCveNum := regexp.MustCompile(`(?mi)CVE-[\d]{1,}-([\d]{1,})$`)
sm := util.RegexpCveNumber.FindAllStringSubmatch(i.Body, -1)
if len(sm) > 0 && len(sm[0]) > 0 {
val := sm[0][1]
tt = util.GetCveNumber(util.TrimString(val))
if tt != "" && regCveNum.Match([]byte(tt)) {
ok = true
}
}
if ok {
pri.Id = i.Id
pri.Number = i.Number
pri.CveNumber = tt
pri.Repo = i.Repository.Path
}
return
}
// Get the pr associated with a single repo
func getRepoIssueAllPR(affectBranch []string, token, owner, repo string, isTemp models.IssueTemplate) (prList []models.PullRequestIssue) {
url := fmt.Sprintf("https://gitee.com/api/v5/repos/%v/issues/%v/pull_requests", owner, isTemp.IssueNum)
req, err := http.NewRequest(http.MethodGet, url, nil)
if err != nil {
logs.Error("NewRequest, url: ", url, ",err: ", err)
return
}
q := req.URL.Query()
q.Add("access_token", token)
q.Add("repo", repo)
req.URL.RawQuery = q.Encode()
resp, err := http.DefaultClient.Do(req)
if err != nil {
logs.Error("DefaultClient, url: ", url, ",err: ", err)
return
}
if resp.StatusCode == http.StatusOK {
issuePr := make([]map[string]interface{}, 0)
read, err := ioutil.ReadAll(resp.Body)
if err != nil {
logs.Error("ReadAll, url: ", url, ",err: ", err)
return
}
resp.Body.Close()
//logs.Info("getRepoIssueAllPR, body ===> ", read)
err = json.Unmarshal(read, &issuePr)
if err != nil {
logs.Error("Unmarshal, url: ", url, ",err: ", err)
return
}
logs.Info("issuePr: ", issuePr)
for _, brh := range affectBranch {
pr := models.PullRequestIssue{}
pr.Branch = brh
pr.BrFlag = false
for _, v := range issuePr {
if _, ok := v["id"]; !ok {
continue
}
if v["state"].(string) == "merged" && v["mergeable"].(bool) {
if v["base"].(map[string]interface{})["label"].(string) == brh {
pr.Id = int64(v["id"].(float64))
pr.Number = isTemp.IssueNum
pr.CveNumber = isTemp.CveNum
pr.Repo = repo
pr.BrFlag = true
break
}
}
}
prList = append(prList, pr)
}
} else {
resp.Body.Close()
}
return
}
func isNormalCloseIssue(cveID int64, issueState int8) bool {
if issueState == 1 {
return false
}
score, err := models.QueryIssueScore(cveID)
if err != nil {
logs.Error("err: ", err, "\n, cveID: ", cveID)
return false
}
if score.Ostatus != 3 {
return false
}
return true
}
// update data
func updateTempAndCenter(issueTmp models.IssueTemplate, cveCenter models.VulnCenter, token, owner string) bool {
appearErr := 0
affectBrandFlag := false
uNaffected := beego.AppConfig.String("labeUnaffected")
labelFixed := beego.AppConfig.String("labelFixed")
labelUnFix := beego.AppConfig.String("labelUnFix")
labeAbiChanged := beego.AppConfig.String("labeAbiChanged")
path := cveCenter.PackName
sn := models.SecurityNotice{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum}
snErr := sn.Read("cve_id", "cve_num")
if snErr != nil {
logs.Error("err: ", snErr)
} else {
if issueTmp.Status > 2 {
affectBranchsxList := make([]string, 0)
affectedBranchs := ""
if cveCenter.OrganizationID == 4 {
affectedBranchs = strings.ReplaceAll(sn.AffectProduct, "/", ",")
} else if cveCenter.OrganizationID == 3 {
if sn.AffectProduct != "" && len(sn.AffectProduct) > 1 {
tmpTagList := make([]string, 0)
affectProductSlice := strings.Split(sn.AffectProduct, "/")
for _, tags := range affectProductSlice {
mdbt := models.MindSporeBrandTags{PackageName: path, Tags: tags}
mtErr := models.QueryMindSporeBrandTags(&mdbt, "PackageName", "Tags")
if mtErr == nil {
tmpTagList = append(tmpTagList, mdbt.Brand)
}
}
if len(tmpTagList) > 0 {
sn.AffectProduct = strings.Join(tmpTagList, "/")
affectedBranchs = strings.ReplaceAll(sn.AffectProduct, "/", ",")
} else {
sn.AffectProduct = ""
}
}
} else if cveCenter.OrganizationID == 2 {
affectedBranchs = strings.ReplaceAll(sn.AffectProduct, "/", ",")
} else {
affectedBranchs = beego.AppConfig.String("cve::affected_branchs")
}
if affectedBranchs != "" && len(affectedBranchs) > 0 {
affectBranchsxList = strings.Split(affectedBranchs, ",")
}
if sn.AffectProduct != "" && len(sn.AffectProduct) > 1 {
affectProductList := strings.Split(sn.AffectProduct, "/")
if len(affectProductList) > 0 {
for _, brands := range affectProductList {
if len(affectBranchsxList) > 0 {
for _, affectBranch := range affectBranchsxList {
if affectBranch == brands {
affectBrandFlag = true
break
}
}
}
if affectBrandFlag {
break
}
}
}
}
}
switch issueTmp.IssueStatus {
case 2:
sn.AffectStatus = "Fixed"
case 6:
sn.AffectStatus = "UnAffected"
default:
sn.AffectStatus = "UnFixed"
}
err := sn.Update("affect_status")
if err != nil {
appearErr++
logs.Error("err: ", err, ", issueTmp.IssueStatus: ", issueTmp.IssueStatus)
}
}
cveCenter.CveLevel = models.OpenEulerScoreProc(issueTmp.NVDScore)
if len(cveCenter.CveVersion) > 0 && cveCenter.CveVersion[0] == ',' {
cveCenter.CveVersion = cveCenter.CveVersion[1:]
}
update := models.UpdateVulnCenter(&cveCenter, "is_export", "cve_level", "CveVersion")
if !update {
logs.Error("update vulnCenter fail ")
appearErr += 1
}
if issueTmp.StatusName == "closed" || issueTmp.Status == 3 {
if affectBrandFlag {
issueTmp.IssueLabel = labelFixed
} else {
issueTmp.IssueLabel = uNaffected
}
} else {
issueTmp.IssueLabel = labelUnFix
}
labelSlice := make([]string, 0)
labelSlice = taskhandler.AddLabelValue(token, path, issueTmp.IssueNum, owner, issueTmp.IssueLabel, 1)
if len(labelSlice) > 0 {
labelStr := strings.Join(labelSlice, ",")
if issueTmp.Status == 3 {
if affectBrandFlag {
if strings.Contains(labelStr, uNaffected) {
labelSlice = common.DeleteSliceValue(labelSlice, uNaffected)
}
if !strings.Contains(labelStr, labelFixed) {
labelSlice = append(labelSlice, labelFixed)
}
} else {
if strings.Contains(labelStr, labelFixed) {
labelSlice = common.DeleteSliceValue(labelSlice, labelFixed)
}
if !strings.Contains(labelStr, uNaffected) {
labelSlice = append(labelSlice, uNaffected)
}
}
if strings.Contains(labelStr, labelUnFix) {
labelSlice = common.DeleteSliceValue(labelSlice, labelUnFix)
}
} else {
if !strings.Contains(labelStr, labelUnFix) {
labelSlice = append(labelSlice, labelUnFix)
}
}
} else {
if issueTmp.Status == 3 {
if affectBrandFlag {
labelSlice = append(labelSlice, labelFixed)
} else {
labelSlice = append(labelSlice, uNaffected)
}
} else {
labelSlice = append(labelSlice, labelUnFix)
}
}
issueTmp.IssueLabel = strings.Join(labelSlice, ",")
if issueTmp.Status == 3 && cveCenter.OrganizationID == 1 &&
AbiAffectedVersionBool(issueTmp.AbiVersion) && !strings.Contains(issueTmp.IssueLabel, labeAbiChanged) {
issueTmp.IssueLabel = issueTmp.IssueLabel + "," + labeAbiChanged
}
updateLabels := true
templateInDB := models.IssueTemplate{TemplateId: issueTmp.TemplateId}
if err := models.GetIssueTemplateByColName(&templateInDB, "template_id"); err == nil {
if templateInDB.IssueLabel == issueTmp.IssueLabel {
updateLabels = false
}
}
if updateLabels {
update = taskhandler.UpdateIssueLabels(token, path, issueTmp.IssueNum, owner, issueTmp.IssueLabel)
if !update {
logs.Error("update gitee issue label fail ,", issueTmp.IssueNum, issueTmp.IssueLabel)
appearErr++
}
}
issueTmp.CveLevel = models.OpenEulerScoreProc(issueTmp.OpenEulerScore)
tpErr := models.UpdateIssueTemplate(&issueTmp, "status", "issue_status",
"status_name", "issue_label", "mt_audit_flag", "sa_audit_flag", "cve_level")
if tpErr != nil {
logs.Error("tpErr: ", tpErr)
appearErr += 1
}
if appearErr > 0 {
logs.Error("handle issue state hook appear error maybe some step "+
"fail, appearErr: ", appearErr, ",issuetmp: ", issueTmp)
return false
}
return true
}
func AbiAffectedVersionBool(abiVersion string) bool {
abiVersionList := []string{}
brandsGroup := strings.Split(abiVersion, ",")
if len(brandsGroup) > 0 {
for _, brand := range brandsGroup {
if brand == "" || len(brand) < 2 {
continue
}
brand = common.BranchVersionRep(brand)
brandList := strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if prams == "是" {
abiVersionList = append(abiVersionList, brandList[0])
break
}
} else {
brandList = strings.Split(brand, ":")
if len(brandList) > 1 {
prams := strings.Replace(brandList[1], " ", "", -1)
if prams == "是" {
abiVersionList = append(abiVersionList, brandList[0])
break
}
}
}
}
}
if len(abiVersionList) > 0 {
return true
}
return false
}
func openEulerScoreReview(issueTmp *models.IssueTemplate, cuAccount, owner, token string) bool {
approveFlag := true
if isReviewer(cuAccount) {
issueTmp.OpAuditFlag = 1
approveFlag = false
err := changeOpenEulerScoreStatus(issueTmp.CveId, 3)
if err != nil {
logs.Error("1.err: ", err, ",issueTmp: ", issueTmp)
} else {
err = models.UpdateIssueTemplate(issueTmp, "op_audit_flag")
if err != nil {
logs.Error("2.err: ", err, ",issueTmp: ", issueTmp)
}
taskhandler.AddCommentToIssue(fmt.Sprintf(ReviewApproveScore, issueTmp.Assignee, cuAccount),
issueTmp.IssueNum, owner, issueTmp.Repo, token)
}
} else {
taskhandler.AddCommentToIssue(fmt.Sprintf(CommentReviewRemindMaintainer, cuAccount),
issueTmp.IssueNum, owner, issueTmp.Repo, token)
}
return approveFlag
}
func otherMaintainerApprove(
issueTmp *models.IssueTemplate, cuAccount, owner, token, fixed, unfixed, path string, cveCenter models.VulnCenter,
) {
unFixList := taskhandler.CheckAffectVerComplete(issueTmp.AffectedVersion, issueTmp.Repo,
issueTmp.OwnedVersion, cveCenter.OrganizationID)
if len(unFixList) > 0 {
na := "\n**请确认分支信息是否填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(CommentCheckVersion, cuAccount, strings.Join(unFixList, ",")) + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, path, token)
return
}
var (
check = taskhandler.CheckOtherIssueClosedAnalysisComplete
review func(string2 string) bool
)
switch cveCenter.OrganizationID {
case util.OpenGauss:
review = isGaussReviewer
case util.MindSpore:
review = isMindSporeReviewer
case util.OpenLookeng:
review = isLooKengReviewer
}
if check == nil || review == nil {
return
}
if _, tb, ok := check(issueTmp, &cveCenter); !ok {
//send comment to issue
na := "\n**请确认分析内容的准确性,待分析内容请填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(AnalysisComplete, cuAccount) + tb + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, path, token)
return
} else {
if !review(cuAccount) && issueTmp.Assignee != cuAccount {
logs.Error("Invalid user review, cuAccount: ", cuAccount)
taskhandler.AddCommentToIssue(fmt.Sprintf(`@%v maintainer具有通过(/approve或者/close)关闭issue, 否则请通过issue页面按钮关闭issue!`,
cuAccount), issueTmp.IssueNum, owner, path, token)
return
}
issueTmp.IssueLabel = unfixed
issueTmp.StatusName = "open"
issueTmp.Status = 1
assignee := "@" + issueTmp.Assignee
issuePrFlag := VerifyIssueAsPr(issueTmp, cveCenter, false, assignee, cuAccount)
if issuePrFlag {
issueTmp.IssueLabel = fixed
issueTmp.StatusName = "closed"
taskhandler.AddCommentToIssue(fmt.Sprintf(`@%v 你已审核模板内容, cve-manager 将关闭issue!`,
cuAccount), issueTmp.IssueNum, owner, path, token)
_, issueErr := taskhandler.UpdateIssueToGit(token, owner, issueTmp.Repo,
cveCenter, *issueTmp)
if issueErr == nil {
logs.Info("Initiate an issue to close,issuetmp: ", issueTmp)
} else {
logs.Error("Issue closing operation failed, issuetmp: ", issueTmp, ",issueErr: ", issueErr)
return
}
//issueTmp.SaAuditFlag = 1
issueTmp.Status = 3
if isNormalCloseIssue(issueTmp.CveId, issueTmp.IssueStatus) {
issueTmp.IssueStatus = 2
cveCenter.IsExport = 3
} else {
issueTmp.IssueStatus = 6
cveCenter.IsExport = 2
}
updateBool := updateTempAndCenter(*issueTmp, cveCenter, token, owner)
if !updateBool {
return
}
}
}
}
func maintainerApprove(issueTmp *models.IssueTemplate, cuAccount, owner, token, fixed,
unfixed string, organizationID int8, payload *models.CommentPayload) {
if msg, _, ok := taskhandler.CheckIssueAnalysisComplete(issueTmp); !ok {
//send comment to issue
na := "\n**请确认分析内容的准确性,待分析内容请填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(CommentAnalysisCplTpl, cuAccount, msg) + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
return
} else {
issueTmp.MtAuditFlag = 1
err := models.UpdateIssueTemplate(issueTmp, "mt_audit_flag")
if err != nil {
logs.Error("upErr: ", err, "issueTmp: ", issueTmp)
return
}
cveCenter := models.VulnCenter{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum}
err = models.GetVulnCenterByCid(&cveCenter, "cve_id", "cve_num")
if err != nil {
return
}
if err = SetIssueStateByReason(issueTmp, payload.Issue.StateName); err != nil {
logs.Error(webhookCommentLogTag, "SetIssueStateByReason, err: ", err, ",issueTmp: ", issueTmp.IssueNum)
}
if issueTmp.IsIssueComplete() {
issueTmp.ResetLabel()
}
updateTempAndCenter(*issueTmp, cveCenter, token, owner)
return
}
}
func securityApprove(issueTmp *models.IssueTemplate, cuAccount, owner, token,
fixed, unfixed string, organizationID int8, payload *models.CommentPayload) {
if msg, _, ok := taskhandler.CheckIssueAnalysisComplete(issueTmp); !ok {
//send comment to issue
na := "\n**请确认分析内容的准确性,待分析内容请填写完整,否则将无法关闭当前issue.**"
cc := fmt.Sprintf(CommentAnalysisCplTpl, cuAccount, msg) + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, token)
return
} else {
if !isReviewer(cuAccount) && issueTmp.Assignee != cuAccount {
taskhandler.AddCommentToIssue(fmt.Sprintf(`@%v maintainer具有通过(/approve或者/close)关闭issue, 否则请通过issue页面按钮关闭issue!`,
cuAccount), issueTmp.IssueNum, owner, issueTmp.Repo, token)
return
}
cveCenter := models.VulnCenter{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum}
err := models.GetVulnCenterByCid(&cveCenter, "cve_id", "cve_num")
if err != nil {
return
}
if err = SetIssueStateByReason(issueTmp, payload.Issue.StateName); err != nil {
logs.Error(webhookCommentLogTag, "SetIssueStateByReason, err: ", err, ",issueTmp: ", issueTmp.IssueNum)
}
if issueTmp.IsIssueComplete() {
issueTmp.ResetLabel()
}
updateTempAndCenter(*issueTmp, cveCenter, token, owner)
return
}
}
func handleIssueComment(payload models.CommentPayload) {
if payload.Issue == nil || payload.Comment == nil {
logs.Error(webhookCommentLogTag, "issue or comment is nil")
return
}
if payload.Comment.User == nil {
logs.Error(webhookCommentLogTag, "user is nil")
return
}
// The default timeout for receiving hooks
logs.Info("payload.Comment: ", payload.Comment, ", Number: ", payload.Issue.Number, "id: ", payload.Issue.Id)
logs.Error(webhookCommentLogTag, "receive hook of issue num: ", payload.Issue.Number)
issueNum := payload.Issue.Number //issue number string
issueId := payload.Issue.Id // issue id int64
cBody := payload.Comment.Body //Comment subject
cuAccount := payload.Comment.User.UserName //gitee domain address
repoPath := payload.Issue.Repository.Path
cmdRej := beego.AppConfig.DefaultString("rejectCmd", "/reject")
cmdApe := beego.AppConfig.DefaultString("approveCmd", "/approve")
cmdClose := beego.AppConfig.DefaultString("closeCmd", "/close")
cmdError := beego.AppConfig.DefaultString("errorCmd", "/error")
cmdAutoPr := beego.AppConfig.String("autoPrCmd")
cmdGetCve := beego.AppConfig.String("getCveCmd")
cmdFeedBack := beego.AppConfig.String("feedBackCmd")
verifyCmd := beego.AppConfig.String("verifyCmd")
if issueNum == "" || cuAccount == "" || cBody == "" {
logs.Error(webhookCommentLogTag, "Data has null values: issueNum, cuAccount, cBody:")
return
}
cBody = strings.ReplaceAll(cBody, ":", ":")
// Ignore this comment
botCuAccountStr := beego.AppConfig.String("cve::bot_cu_account")
botCuAccountList := strings.Split(botCuAccountStr, ",")
if len(botCuAccountList) > 0 {
for _, botCu := range botCuAccountList {
if cuAccount == botCu {
logs.Error(webhookCommentLogTag, cuAccount, ", Ignore this comment")
return
}
}
}
issueTmp := models.IssueTemplate{IssueNum: issueNum, IssueId: issueId}
err := models.GetIssueTemplateByColName(&issueTmp, "issue_num", "issue_id")
if err != nil {
logs.Error(webhookCommentLogTag, "get issue template error", issueNum, err)
return
}
agencyPram := AgencyPrams{}
fixed := beego.AppConfig.String("labelFixed")
unfixed := beego.AppConfig.String("labelUnFix")
path := issueTmp.Repo
vc := models.VulnCenter{CveId: issueTmp.CveId}
vcErr := models.GetVulnCenterByCid(&vc, "CveId")
if vcErr != nil {
logs.Error(webhookCommentLogTag, "get vuln center error", issueTmp.CveId, vcErr)
return
}
owner, accessToken := common.GetOwnerAndToken(vc.CveNum, vc.OrganizationID)
if vc.OrganizationID == 2 {
if len(path) < 2 {
path = beego.AppConfig.String("opengauss::gauss_issue_path")
}
cBody = strings.ReplaceAll(cBody, util.KwOpenGaussScore, util.KwOpenEulerScore)
return
} else if vc.OrganizationID == 3 {
// Query the repo that needs to submit an issue
cBody = strings.ReplaceAll(cBody, util.KwMindSporeScore, util.KwOpenEulerScore)
return
} else if vc.OrganizationID == 4 {
cBody = strings.ReplaceAll(cBody, util.KwLooKengScore, util.KwOpenEulerScore)
return
}
if len(repoPath) > 1 && repoPath != path {
vc.PackName = repoPath
vc.RepoName = issueTmp.OwnedComponent
models.UpdateVulnCenter(&vc, "PackName", "RepoName")
issueTmp.Repo = repoPath
models.UpdateIssueTemplate(&issueTmp, "Repo")
path = repoPath
}
if payload.Issue.StateName == "已拒绝" {
logs.Error("The current issue has been rejected and will not be processed, payload: ", payload)
issueTmp.Status = 4
issueTmp.StatusName = "rejected"
models.UpdateIssueTemplate(&issueTmp, "Status", "StatusName")
taskhandler.AddCommentToIssue(fmt.Sprintf(CommentRejectedState, cuAccount, payload.Issue.StateName),
issueTmp.IssueNum, owner, path, accessToken)
return
}
if strings.HasPrefix(cBody, cmdRej) {
//Review rejected Add comment @Analyst
if !isReviewer(cuAccount) {
return
}
if issueTmp.OpenEulerScore != issueTmp.NVDScore && issueTmp.NVDScore > 0 &&
issueTmp.OpenEulerScore > 0 {
err = changeOpenEulerScoreStatus(issueTmp.CveId, 2)
if err != nil {
logs.Error("changeOpenEulerScoreStatus, 1.err:", err)
}
issueTmp.OpAuditFlag = 2
err = models.UpdateIssueTemplate(&issueTmp, "op_audit_flag")
if err != nil {
logs.Error("UpdateIssueTemplate, 2.err:", err)
}
taskhandler.AddCommentToIssue(fmt.Sprintf(ReviewRejectScore, issueTmp.Assignee, cuAccount),
issueTmp.IssueNum, owner, path, accessToken)
}
} else if strings.HasPrefix(cBody, cmdApe) || strings.HasPrefix(cBody, cmdClose) {
if issueTmp.Status == 3 {
// The issue has been closed and cannot be operated again
logs.Error("The issue has been closed and cannot be operated again,issuetmp: ", issueTmp)
return
}
if vc.OrganizationID != 1 {
comLock.Lock()
otherMaintainerApprove(&issueTmp, cuAccount, owner, accessToken, fixed, unfixed, path, vc)
comLock.Unlock()
} else {
approveFlag := true
if issueTmp.OpenEulerScore != issueTmp.NVDScore && issueTmp.NVDScore > 0 &&
issueTmp.OpAuditFlag != 1 && issueTmp.OpenEulerScore > 0 {
//Approved to modify the rating status
approveFlag = openEulerScoreReview(&issueTmp, cuAccount, owner, accessToken)
}
if approveFlag {
mtAuditFlag := false
// Analysis command belongs to the time period
maintainerList, mainOk := models.QueryRepoAllMaintainer(issueTmp.Repo)
if mainOk && len(maintainerList) > 0 {
for _, v := range maintainerList {
if util.TrimString(v.MemberName) == cuAccount {
mtAuditFlag = true
break
}
}
}
if mtAuditFlag {
comLock.Lock()
maintainerApprove(&issueTmp, cuAccount, owner, accessToken, fixed, unfixed, vc.OrganizationID, &payload)
comLock.Unlock()
} else {
comLock.Lock()
securityApprove(&issueTmp, cuAccount, owner, accessToken, fixed, unfixed, vc.OrganizationID, &payload)
comLock.Unlock()
}
}
}
} else if strings.HasPrefix(cBody, cmdError) {
c := strings.TrimPrefix(cBody, cmdError)
c = strings.TrimSpace(c)
issueTmp.ErrorDescription = c
issueTmp.UpdateTime = time.Now()
//update issue tpl
err = models.UpdateIssueTemplate(&issueTmp, "error_description", "update_time")
if err != nil {
logs.Error("updateIssueErr: ", err, "issueTmp: ", issueTmp)
return
}
} else if strings.HasPrefix(cBody, cmdAutoPr) {
if vc.OrganizationID == 1 {
PostCveAgency(issueTmp, agencyPram)
}
} else if strings.HasPrefix(cBody, cmdFeedBack) {
if vc.OrganizationID == 1 {
bodyList := strings.Split(cBody, " ")
if len(bodyList) > 2 {
agencyPram.CveUrl = bodyList[1]
agencyPram.PatchUrl = bodyList[2]
}
PostCveAgency(issueTmp, agencyPram)
}
} else if strings.HasPrefix(cBody, cmdGetCve) {
PostTriggerGetCve(issueTmp, owner, accessToken, cuAccount)
} else if strings.HasPrefix(cBody, verifyCmd) {
if vc.OrganizationID == 1 {
VerifyCve(issueTmp)
}
} else if strings.HasPrefix(cBody, cmdCheckIssue) {
comLock.Lock()
checkAndUpdateIssue(&payload, issueTmp, owner, accessToken, path, cuAccount)
comLock.Unlock()
} else {
logs.Error(webhookCommentLogTag, "analysis comment, number: ", payload.Issue.Number)
if payload.Issue.State == "closed" || payload.Issue.State == "rejected" ||
payload.Issue.State == "已完成" || payload.Issue.State == "已拒绝" {
logs.Error(webhookCommentLogTag, "Cannot edit comment, number: ", payload.Issue.Number)
return
}
cBody = strings.ReplaceAll(cBody, ":", ":")
comLock.Lock()
analysisComment(owner, accessToken, path, cuAccount, cBody, &payload, issueTmp, vc)
comLock.Unlock()
}
}
func PostCveAgency(issueTmp models.IssueTemplate, agencyPram AgencyPrams) {
// Trigger third-party data generation
agencyUrl := beego.AppConfig.String("cveagency::url")
agencyReq := make(map[string]interface{}, 0)
agencyReq["CveNum"] = issueTmp.CveNum
agencyReq["PackName"] = issueTmp.OwnedComponent
agencyReq["PackVersion"] = issueTmp.OwnedVersion
agencyReq["CveUrl"] = agencyPram.CveUrl
agencyReq["PatchUrl"] = agencyPram.PatchUrl
if len(issueTmp.AffectedVersion) > 0 {
versionList := strings.Split(issueTmp.AffectedVersion, ",")
if len(versionList) > 0 {
vSlice := []string{}
for _, vl := range versionList {
vs := strings.Split(vl, ":")
if len(vs) > 0 {
vSlice = append(vSlice, vs[0])
}
}
if len(vSlice) > 0 {
agencyReq["AffectBranch"] = strings.Join(vSlice, " ")
}
}
} else {
agencyReq["AffectBranch"] = ""
}
agencyReq["IssueNumber"] = issueTmp.IssueNum
util.HTTPPostCom(agencyReq, agencyUrl)
}
func VerifyCve(issueTmp models.IssueTemplate) {
agencyUrl := beego.AppConfig.String("cveagency::url")
agencyReq := make(map[string]interface{}, 0)
agencyReq["CveNum"] = issueTmp.CveNum
agencyReq["PackName"] = issueTmp.OwnedComponent
agencyReq["Verify"] = true
agencyReq["IssueNumber"] = issueTmp.IssueNum
util.HTTPPostCom(agencyReq, agencyUrl)
}
func PostTriggerGetCve(issueTmp models.IssueTemplate, owner, accessToken, cuAccount string) {
// Trigger third-party data generation
cc := ""
if len(issueTmp.CveNum) > 0 {
resp := tabletask.PullCve(issueTmp.CveNum)
if resp == 1 {
cc = fmt.Sprintf(CommentGetNvdCveSuccess, cuAccount)
} else {
cc = fmt.Sprintf(CommentGetNvdCveFailed, cuAccount)
}
if len(cc) > 1 {
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, issueTmp.Repo, accessToken)
}
vc := models.VulnCenter{CveId: issueTmp.CveId}
vcErr := models.GetVulnCenterByCid(&vc, "CveId")
if vcErr != nil {
logs.Error("GetVulnCenterByCid, vcErr: ", vcErr, ",CveId: ", issueTmp.CveId)
return
}
models.UpdateIssueStatus(vc, 1)
issueErr := task.SyncCreateIssue([]string{issueTmp.CveNum})
if issueErr != nil {
logs.Error("SyncCveAndIssue, SyncCreateIssue, err: ", issueErr)
}
}
}
func isReviewer(path string) bool {
sr := models.Reviewer{NameSpace: path, OrganizationID: 1}
return sr.Read("name_space", "organizate_id")
}
func isGaussReviewer(path string) bool {
sr := models.Reviewer{NameSpace: path, OrganizationID: 2}
return sr.Read("name_space", "organizate_id")
}
func isMindSporeReviewer(path string) bool {
sr := models.Reviewer{NameSpace: path, OrganizationID: 3}
return sr.Read("name_space", "organizate_id")
}
func isLooKengReviewer(path string) bool {
sr := models.Reviewer{NameSpace: path, OrganizationID: 4}
return sr.Read("name_space", "organizate_id")
}
// newValues 来自0day系统评论时,分支数量会比originValues少
// newValues 来自issue评论时,分支数量和originValues一样
// 当issue模板新增受影响分支时,新增时间之前已创建的issue,要想通过检查,newValues的数量会比originValues更多
func replaceValue(originValues, newValues string) string {
if originValues == "" {
return newValues
}
originSplit := strings.Split(originValues, ",")
newSplit := strings.Split(newValues, ",")
const splitLength = 2
var notMatched []string
for _, nv := range newSplit {
splitNV := strings.Split(nv, ":")
if len(splitNV) != splitLength {
continue
}
var matched bool
for k, ov := range originSplit {
splitOV := strings.Split(ov, ":")
if len(splitOV) != splitLength {
continue
}
if splitNV[0] == splitOV[0] {
originSplit[k] = nv
matched = true
break
}
}
if !matched {
notMatched = append(notMatched, nv)
}
}
mergedSplit := append(originSplit, notMatched...)
return strings.Join(mergedSplit, ",")
}
func analysisComment(owner, accessToken, path string, cuAccount string, cBody string,
payload *models.CommentPayload, issueTmp models.IssueTemplate, v models.VulnCenter) {
if issueTmp.Status == 3 {
// The issue has been closed and cannot be operated again
logs.Error(webhookCommentLogTag, "The issue has been closed and cannot be operated again ", issueTmp.IssueNum)
return
}
canVerfy := false
issueTmp.MtAuditFlag = 1
//is Analyst comment and content start with '/analysis'
vMap, err := util.ExtractCommentAnalysisAllValue(cBody, v.OrganizationID)
if err != nil {
taskhandler.AddCommentToIssue(err.Error(), issueTmp.IssueNum, owner, path, accessToken)
return
}
logs.Error(webhookCommentLogTag, "vMap of ", issueTmp.IssueNum, vMap)
if len(vMap) > 0 {
canVerfy = true
cols := make([]string, 0)
for k, v := range vMap {
switch k {
case "cve_analysis":
if v != "" && len(v) > 1 {
issueTmp.CveAnalysis = common.DeletePreAndSufSpace(v)
cols = append(cols, k)
}
case "principle_analysis":
issueTmp.PrincipleAnalysis = v
cols = append(cols, k)
case "openeuler_score":
fv, err := strconv.ParseFloat(v, 64)
if err == nil && fv > 0 {
if issueTmp.OpenEulerScore > 0 && issueTmp.OpenEulerScore != fv {
issueTmp.OpAuditFlag = 0
cols = append(cols, "op_audit_flag")
}
issueTmp.OpenEulerScore = fv
// Dominated by openeuler score, update cve level
issueTmp.CveLevel = models.OpenEulerScoreProc(fv)
cols = append(cols, k, "cve_level")
}
case "openeuler_vector":
if v != "" && len(v) > 1 {
issueTmp.OpenEulerVector = common.DeletePreAndSufSpace(v)
cols = append(cols, k)
}
case "affected_version":
if v != "" && len(v) > 1 {
if paraAffectBrandBool(v) {
issueTmp.AffectedVersion = replaceValue(issueTmp.AffectedVersion, v)
cols = append(cols, k)
}
}
case "abi_version":
if v != "" && len(v) > 1 {
if taskhandler.ParaAffectAbiBool(v) {
issueTmp.AbiVersion = replaceValue(issueTmp.AbiVersion, v)
cols = append(cols, k)
}
}
case "analysis_version":
if v != "" && len(v) > 1 {
if paraAnalysisBrandBool(v) {
issueTmp.AnalysisVersion = replaceValue(issueTmp.AnalysisVersion, v)
cols = append(cols, k)
}
}
case "solution":
issueTmp.Solution = v
cols = append(cols, k)
}
}
if len(cols) > 0 {
cols = append(cols, "mt_audit_flag")
err := models.UpdateIssueTemplate(&issueTmp, cols...)
if err != nil {
logs.Error(webhookCommentLogTag, "update issue template failed", issueTmp.IssueNum, err)
} else {
if _, ok := vMap["openeuler_vector"]; ok {
err := saveVectorData(vMap["openeuler_vector"], issueTmp.CveId)
if err != nil {
logs.Error(webhookCommentLogTag, "saveVectorData, err: ", err)
}
}
if _, ok := vMap["openeuler_score"]; ok {
//更新分数到 score
score, err := models.QueryIssueScore(issueTmp.CveId)
if err != nil {
logs.Error(webhookCommentLogTag, "query score failed", issueTmp.CveId, err)
} else {
score.OpenEulerScore = issueTmp.OpenEulerScore
score.Ostatus = 1
err := models.UpdateScore(&score, "openeuler_score", "o_score_status")
if err != nil {
logs.Error(webhookCommentLogTag, "update score failed", err)
}
}
}
}
// update gitee issue
commentUpdateIssue(issueTmp, owner, accessToken, path)
}
}
logs.Error(webhookCommentLogTag, "canVerfy of ", issueTmp.IssueNum, canVerfy)
if canVerfy {
checkAndUpdateIssue(payload, issueTmp, owner, accessToken, path, cuAccount)
}
}
func checkAndUpdateIssue(payload *models.CommentPayload, issueTmp models.IssueTemplate,
owner, accessToken, path, cuAccount string) {
if msg, tb, ok := taskhandler.CheckIssueAnalysisComplete(&issueTmp); !ok {
//send comment to issue
issueTmp.IssueStatus = 1
err := models.UpdateIssueTemplate(&issueTmp, "issue_status")
if err != nil {
logs.Error(webhookCommentLogTag, "UpdateIssueTemplate, upErr: ", err, ",issueTmp: ", issueTmp.IssueNum)
}
assignee := ""
if cuAccount != "" {
assignee = cuAccount
} else {
assignee = issueTmp.Assignee
}
msg = fmt.Sprintf(CommentAnalysisCplTpl, assignee, msg)
taskhandler.AddCommentToIssue(msg, issueTmp.IssueNum, owner, path, accessToken)
} else {
//1. change issue status
issueTmp.IssueStatus = 3
//2. Are the cvsScore and openEuler score equal .If not equal, notify the auditor to review .
var na string
if issueTmp.OpenEulerScore != issueTmp.NVDScore && issueTmp.OpenEulerScore > 0 &&
issueTmp.NVDScore > 0 && issueTmp.OpAuditFlag == 0 {
na = "\n**因OpenEulerScore与NvdScore不一致,分析内容需审核,请等待安全组审核!**"
//Notify the responsible person for review
notifyAuditorReview(payload, issueTmp)
} else {
if issueTmp.MtAuditFlag == 0 {
maintainerList, mainOk := models.QueryRepoAllMaintainer(issueTmp.Repo)
assList := []string{}
if mainOk && len(maintainerList) > 0 {
for _, v := range maintainerList {
assList = append(assList, "@"+v.MemberName+" ")
content := fmt.Sprintf("%v 仓库的CVE和安全问题的ISSUE,CVE编号: %v, "+
"已经完成了模板填写,需要您对填写的内容进行审核,审核通过才能进行后续操作.", issueTmp.Repo, issueTmp.CveNum)
taskhandler.SendPrivateLetters(accessToken, content, v.MemberName)
}
}
assignee := ""
if len(assList) > 0 {
assignee = strings.Join(assList, ",")
} else {
assignee = "@" + issueTmp.Assignee
}
na = "\n**请确认模板分析内容的准确性与完整性, 确认无误后,请在评论区输入: /approve, 否则无法关闭当前issue.**"
cc := fmt.Sprintf(ContentReview, assignee) + tb + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, path, accessToken)
} else {
na = "\n**请确认分析内容的准确性, 确认无误后, 您可以进行后续步骤, 否则您可以继续分析.**"
cc := fmt.Sprintf(AnalysisComplete, issueTmp.Assignee) + tb + na
taskhandler.AddCommentToIssue(cc, issueTmp.IssueNum, owner, path, accessToken)
}
// change score status
err := changeOpenEulerScoreStatus(issueTmp.CveId, 3)
if err != nil {
logs.Error(webhookCommentLogTag, "changeOpenEulerScoreStatus, err: ", err, ",issueTmp: ", issueTmp.IssueNum)
}
if err = SetIssueStateByReason(&issueTmp, payload.Issue.StateName); err != nil {
logs.Error(webhookCommentLogTag, "SetIssueStateByReason, err: ", err, ",issueTmp: ", issueTmp.IssueNum)
}
}
}
err := models.UpdateIssueTemplate(&issueTmp, "issue_status", "mt_audit_flag")
if err != nil {
logs.Error(webhookCommentLogTag, "UpdateIssueTemplate, updErr: ", err, ",issueTmp: ", issueTmp.IssueNum)
}
}
func notifyAuditorReview(payload *models.CommentPayload, issueTmp models.IssueTemplate) {
//Notify the responsible person for review
list, err := models.GetSecurityReviewerList()
if err != nil {
logs.Error("GetSecurityReviewerList, err: ", err)
return
}
if len(list) == 0 {
return
}
accessToken := os.Getenv("GITEE_TOKEN")
content := fmt.Sprintf(ReviewPrivateLettersTpl,
payload.Issue.Title, payload.Issue.HtmlUrl, issueTmp.NVDScore, issueTmp.OpenEulerScore)
owner := beego.AppConfig.String("gitee::owner")
//path := beego.AppConfig.String("gitee::path")
path := issueTmp.Repo
ns := make([]string, len(list))
for k, v := range list {
ns[k] = "@" + v.NameSpace + " "
taskhandler.SendPrivateLetters(accessToken, content, v.NameSpace)
//add @comment
}
msg := fmt.Sprintf(CommentReviewTpl, strings.Join(ns, ","))
taskhandler.AddCommentToIssue(msg, issueTmp.IssueNum, owner, path, accessToken)
msg = fmt.Sprintf("@%s ,请给出NVD评分和openEuler评分不一致的理由", payload.Comment.User.Login)
taskhandler.AddCommentToIssue(msg, issueTmp.IssueNum, owner, path, accessToken)
}
func changeOpenEulerScoreStatus(cveID int64, status int8) error {
score, err := models.QueryIssueScore(cveID)
if err != nil {
return err
}
score.Ostatus = status
err = models.UpdateScore(&score, "o_score_status")
return err
}
func commentUpdateIssue(issueTmp models.IssueTemplate, owner, accessToken, path string) {
if accessToken != "" && owner != "" && path != "" {
cvlnCenter := models.VulnCenter{}
err := models.GetVulnCenterByCVEID(&cvlnCenter, issueTmp.CveId)
if err != nil {
logs.Error(webhookCommentLogTag, "get vuln center failed", issueTmp.CveId, err)
} else {
_, err := taskhandler.UpdateIssueToGit(accessToken, owner, path, cvlnCenter, issueTmp)
if err != nil {
logs.Error(webhookCommentLogTag, "UpdateIssueToGit, upErr: ", err, ",num: ", issueTmp.IssueNum)
}
}
}
}
func saveVectorData(vct string, cveID int64) error {
score, err := models.QueryIssueScore(cveID)
if err != nil {
return err
}
if vct == "" {
return errors.New("vct value is empty")
}
upFields := make([]string, 0)
score.OvectorVule = vct
upFields = append(upFields, "o_vector_value")
if len(upFields) > 0 {
//Perform update
err = models.UpdateScore(&score, upFields...)
if err != nil {
return err
}
}
return nil
}
func handleCommentPackage(packageStr string, cveID int64) error {
packageStr = util.TrimString(packageStr)
err := models.UpdatePackageByCveId(packageStr, cveID)
if err != nil {
return err
}
return nil
}
// CloseIssue close gitee issue
func CloseIssue(token, repo, issueNum, owner string) bool {
url := fmt.Sprintf("https://gitee.com/api/v5/repos/%s/issues/%s", owner, issueNum)
param := struct {
AccessToken string `json:"access_token"`
Repo string `json:"repo"`
State string `json:"state"`
}{token, repo, "closed"}
pj, err := json.Marshal(¶m)
if err != nil {
logs.Error("json.Marshal, err:", err, ",issueNum: ", issueNum)
return false
}
return UpdateGiteIssue(url, pj)
}
// UpdateGiteIssue update gitee issue
func UpdateGiteIssue(url string, param []byte) bool {
read := bytes.NewReader(param)
req, err := http.NewRequest(http.MethodPatch, url, read)
if err != nil {
logs.Error("NewRequest, err:", err)
return false
}
defer req.Body.Close()
req.Header.Set("Content-Type", "application/json;charset=UTF-8")
client := http.Client{}
resp, err := client.Do(req)
if err != nil {
logs.Error("client.Do, err:", err)
return false
}
defer resp.Body.Close()
if resp.StatusCode == 200 {
return true
}
return false
}
func AddGitIssue(issueHook *models.IssuePayload, desc, product string) error {
issueTitle := util.TrimString(issueHook.Title)
issueType := util.TrimString(issueHook.Issue.TypeName)
issueNumber := util.TrimString(issueHook.Issue.Number)
issueState := util.TrimString(issueHook.Issue.State)
issueZhState := util.TrimString(issueHook.Issue.StateName)
repoPath := util.TrimString(issueHook.Repository.Path)
nameSpace := util.TrimString(issueHook.Repository.NameSpace)
organizationID := int8(1)
organizationID = taskhandler.GetOrganizationId(nameSpace)
issueTitle = taskhandler.RegMatchCve(issueTitle)
if issueType == CIssueType || strings.HasPrefix(issueTitle, "CVE") {
item := models.GiteOriginIssue{IssueId: issueHook.Issue.Id, Url: issueHook.Issue.HtmlUrl,
Number: issueNumber, State: issueState, Title: issueTitle,
IssueType: issueType, SecurityHole: true,
IssueCreateAt: issueHook.Issue.CreateAt, IssueUpdateAt: issueHook.Issue.UpdateAt,
IssueFinishAt: issueHook.Issue.FinishedAt, IssueCreate: issueHook.Issue.User.Login,
IssueAssignee: issueHook.Assignee.Login, RepoPath: repoPath,
RepoUrl: issueHook.Repository.Url, InfProduct: product,
RepoDesc: desc, IssueState: issueZhState, Owner: nameSpace, OrganizationID: organizationID,
Status: 0}
//vt := util.TrimString(v.Title)
cveStr := ""
if strings.HasPrefix(issueTitle, "CVE") {
cveStr = issueTitle
} else if issueHook.Issue.Body != "" {
// Use regular expressions to intercept the body, which will be improved later
sm := util.RegexpCveNumber.FindAllStringSubmatch(issueHook.Issue.Body, -1)
if len(sm) > 0 && len(sm[0]) > 0 {
cveStr = sm[0][1]
}
}
cveList := []string{}
if len(cveStr) > 1 {
cveRes := util.RegexpCveNumVaule.FindAllStringSubmatch(cveStr, -1)
if len(cveRes) > 0 {
for _, cr := range cveRes {
if len(cr) > 0 {
tCr := util.TrimString(cr[0])
if _, ok := common.FindSliceEm(cveList, tCr); !ok {
cveList = append(cveList, tCr)
}
}
}
}
}
owner, accessToken := common.GetOwnerAndToken("", organizationID)
tokenList := models.QueryAuthTokenInfo()
tokenMap := make(map[int8]models.AuthTokenInfo, len(tokenList))
if len(tokenList) > 0 {
for _, tl := range tokenList {
tokenMap[tl.OrganizationID] = tl
}
}
for _, cve := range cveList {
item.CveNumber = cve
// Check whether the current cve has created an issue
checkRes, issueNum := taskhandler.OPenCheckWhetherIssue(item.CveNumber, repoPath, owner, accessToken, organizationID)
if checkRes {
if organizationID == 1 {
taskhandler.AddCommentToIssue(fmt.Sprintf(CreateIssueReject, issueHook.Issue.User.UserName, item.CveNumber, issueNum),
issueNumber, owner, repoPath, accessToken)
taskhandler.AddCommentToIssue(createIssueRepeat, issueNumber, owner, repoPath, accessToken)
authToken := tokenMap[organizationID]
_ = setReject(authToken.EnId, issueHook.Issue.Id, authToken.AccessToken)
} else {
taskhandler.AddCommentToIssue(fmt.Sprintf(HasCreateIssue, issueHook.Issue.User.UserName, item.CveNumber, issueNum),
issueNumber, owner, repoPath, accessToken)
}
logs.Error("Cve has created an issue, please process the previous issue first, ",
item.CveNumber, repoPath, ", organizationID: ", organizationID)
return errors.New("Ignore the current issue")
}
//vb := util.TrimString(v.Body)
vb := strings.ReplaceAll(issueHook.Issue.Body, ":", ":")
item.Body = vb
item.IssueExistTpl = false
err := item.InsertOrUpdate(2)
if err != nil {
logs.Error("insert or update issue fail:", err)
}
}
return nil
} else {
return errors.New("Ignore the current issue")
}
}
func DelOrgIssue(issueHook *models.IssuePayload, organizationID int8) {
issueTitle := util.TrimString(issueHook.Title)
issueType := util.TrimString(issueHook.Issue.TypeName)
issueNumber := util.TrimString(issueHook.Issue.Number)
issueState := util.TrimString(issueHook.Issue.State)
issueZhState := util.TrimString(issueHook.Issue.StateName)
repoPath := util.TrimString(issueHook.Repository.Path)
nameSpace := util.TrimString(issueHook.Repository.NameSpace)
organizationID = taskhandler.GetOrganizationId(nameSpace)
issueTitle = taskhandler.RegMatchCve(issueTitle)
if issueType == CIssueType || strings.HasPrefix(issueTitle, "CVE") {
// Data deletion record
idr := models.IssueDeleteRecord{IssueId: issueHook.Issue.Id, IssueNum: issueNumber,
DelAssignee: issueHook.Sender.Login, Owner: nameSpace, Repo: repoPath,
DeleteTime: common.GetCurTime(), OrganizationID: organizationID, CveNum: issueTitle}
models.InsertIssueDeleteRecord(&idr)
item := models.GiteOriginIssue{IssueId: issueHook.Issue.Id, Url: issueHook.Issue.HtmlUrl,
Number: issueNumber, State: issueState, Title: issueTitle,
IssueType: issueType, SecurityHole: true,
IssueCreateAt: issueHook.Issue.CreateAt, IssueUpdateAt: issueHook.Issue.UpdateAt,
IssueFinishAt: issueHook.Issue.FinishedAt, IssueCreate: issueHook.Issue.User.Login,
IssueAssignee: "", RepoPath: repoPath,
RepoUrl: issueHook.Repository.Url, InfProduct: "",
RepoDesc: "", IssueState: issueZhState, Owner: nameSpace, OrganizationID: organizationID}
if strings.HasPrefix(issueTitle, "CVE") {
item.CveNumber = issueTitle
} else if issueHook.Issue.Body != "" {
// Use regular expressions to intercept the body and improve it later
sm := util.RegexpCveNumber.FindAllStringSubmatch(issueHook.Issue.Body, -1)
if len(sm) > 0 && len(sm[0]) > 0 {
item.CveNumber = util.TrimString(sm[0][1])
}
}
err := item.Detlete()
if err != nil {
logs.Error("Failed to delete issue:", err)
}
issueTmp := models.IssueTemplate{}
issueTmp.IssueId = issueHook.Issue.Id
issueTmp.IssueNum = issueNumber
issueTmp.Repo = repoPath
issueErr := models.GetIssueTemplateByColName(&issueTmp, "IssueNum", "Repo", "IssueId")
if issueErr != nil {
return
}
issueTmp.Status = 6
issueTmp.IssueStatus = 6
issueTmp.StatusName = "已删除"
tpErr := models.UpdateIssueTemplate(&issueTmp, "status", "issue_status",
"status_name")
if tpErr != nil {
logs.Error("UpdateIssueTemplate, tpErr:", tpErr, ",issueTmp: ", issueTmp)
}
cveCenter := models.VulnCenter{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum, PackName: issueTmp.Repo}
cveErr := models.GetVulnCenterByCid(&cveCenter, "cve_id", "cve_num", "pack_name")
if cveErr != nil {
return
}
cveCenter.Status = 7
update := models.UpdateVulnCenter(&cveCenter, "cve_status")
if !update {
logs.Error("update vulnCenter fail ")
}
}
}
// Entry function for handling issue status
func gitAddIssueProc(issueHook *models.IssuePayload, organizationID int8) error {
owner, token := common.GetOwnerAndToken("", organizationID)
path := issueHook.Repository.Path
// The amount of data processed at a time
prcNum, err := beego.AppConfig.Int("crontab::prcnum")
if err != nil {
logs.Error("crontab::prcnum, error: ", err)
return errors.New("value is nil")
}
// How many days have been processed to date data
days, ok := beego.AppConfig.Int("crontab::days")
if ok != nil {
logs.Error("crontab::days, err:", err)
return ok
}
// openeuler Number start value
cveRef := beego.AppConfig.String("cve::cveref")
openeulerNum, ok := beego.AppConfig.Int("cve::openeulernum")
if ok != nil {
logs.Error("cve::openeulernum, err:", err)
return ok
}
// Determine whether the issue has been created
product, err := taskhandler.GetInfProduct(token, owner, path)
if err != nil {
logs.Error("GetInfProduct, err: ", err)
}
//desc := taskhandler.GetRepoDescription(path)
giErr := AddGitIssue(issueHook, "", product)
if giErr != nil {
logs.Error("AddGitIssue, giErr: ", giErr)
}
// Compatible with created issue data
oki, err := taskhandler.GetCveIssueData(prcNum, days, openeulerNum, cveRef, owner, 1)
if !oki {
logs.Error("ProcCveOriginData, GetCveIssueData, err: ", err)
}
cError := task.CreateIssue()
if cError != nil {
logs.Error("CreateIssue, cError: ", cError)
}
comErr := AddIssueComment(token, owner, path, issueHook.Issue.Number,
issueHook.Issue.User.UserName, issueHook.Issue.Id)
logs.Info("CreateIssueToGit, Issue comment creation result, err: ", comErr)
return comErr
}
func AddIssueComment(token, owner, path, issueNum, assignee string, issueId int64) error {
// Create the first comment
branchList := []string{}
errBrands := errors.New("")
issueTmp := models.IssueTemplate{}
issueTmp.IssueId = issueId
issueTmp.IssueNum = issueNum
issueTmp.Repo = path
issueErr := models.GetIssueTemplateByColName(&issueTmp, "IssueNum", "Repo", "IssueId")
if issueErr != nil {
logs.Error("GetIssueTemplateByColName, err: ", issueErr, ", issue: ", issueTmp)
return issueErr
}
cveCenter := models.VulnCenter{CveId: issueTmp.CveId, CveNum: issueTmp.CveNum, PackName: path}
cveErr := models.GetVulnCenterByCid(&cveCenter, "cve_id", "cve_num", "pack_name")
if cveErr != nil {
return cveErr
}
owner, token = common.GetOwnerAndToken(cveCenter.CveNum, cveCenter.OrganizationID)
if cveCenter.OrganizationID == 4 {
cveList := strings.Split(cveCenter.CveVersion, ",")
if len(cveList) > 0 {
for _, vl := range cveList {
olky := models.OpenLookengYaml{PackageName: cveCenter.RepoName, Version: vl, Repo: cveCenter.PackName}
looKengErr := models.GetOpenLookengYaml(&olky, "PackageName", "Version", "Repo")
if olky.Id > 0 {
path = olky.Repo
break
}
logs.Info("GetOpenLookengYaml, looKengErr: ", looKengErr)
}
}
branchList = taskhandler.CreateBrandAndTags(token, owner, path, cveCenter.OrganizationID)
if branchList == nil || len(branchList) == 0 {
logs.Error("OpenLookeng GetBranchesInfo, Failed to obtain the branch information of the repo, ", path, ", err: ", errBrands)
return errors.New("Failed to obtain branch information")
}
} else if cveCenter.OrganizationID == 3 {
// Query the repo that needs to submit an issue
cveList := strings.Split(cveCenter.CveVersion, ",")
if len(cveList) > 0 {
for _, vl := range cveList {
data, _ := models.GetMindSporeYamlAll(&models.MindSporeYaml{PackageName: cveCenter.RepoName})
for _, v := range data {
version := strings.TrimSpace(string(util.Symbol.ReplaceAll([]byte(v.Version), []byte(""))))
if v.Repo == cveCenter.PackName && taskhandler.JudgeVersion(vl, version, v.Version) {
path = v.Repo
break
}
}
}
}
// Get branch information
branchList = taskhandler.CreateBrandAndTags(token, owner, path, cveCenter.OrganizationID)
if branchList == nil || len(branchList) == 0 {
logs.Error("mindspore GetBranchesInfo, "+
"Failed to obtain the branch information of the repo: ", path, ", err: ", errBrands)
return errors.New("Failed to obtain branch information")
}
} else if cveCenter.OrganizationID == 2 {
path = beego.AppConfig.String("opengauss::openGauss-server")
// Get branch information
branchList, errBrands = taskhandler.GetBranchesInfo(token, owner, path, cveCenter.OrganizationID)
if branchList == nil || len(branchList) == 0 {
logs.Error("Gauss GetBranchesInfo, Failed to obtain the branch information of the repo, ", path, ", err: ", errBrands)
return errors.New("Failed to obtain branch information")
}
} else {
// Get branch information
branchList, errBrands = taskhandler.GetBranchesInfo(token, owner, path, cveCenter.OrganizationID)
if branchList == nil || len(branchList) == 0 {
logs.Error("Euler GetBranchesInfo, Failed to obtain the branch information of the repo, ", path, ", err: ", errBrands)
return errors.New("Failed to obtain branch information")
}
}
// Create issue comment
affectedVersion := ""
if len(branchList) > 0 {
for i, brand := range branchList {
if brand == "" || len(brand) < 2 {
continue
}
brandx := ""
if cveCenter.OrganizationID == 1 {
brandx = common.BranchVersionRep(brand)
brandx = taskhandler.OrgRepoParams(cveCenter.PackName, brandx)
} else {
brandx = brand
}
affectedVersion = affectedVersion + strconv.Itoa(i+1) + "." + brandx + ":\n"
}
} else {
affectedVersion = affectedVersion + "\n"
}
_, assignLoginList, _ := taskhandler.GetRepoMember(token, owner, path)
errx := taskhandler.CreateIssueComment(token, owner, path, assignee,
cveCenter, issueNum, affectedVersion, assignLoginList)
if cveCenter.OrganizationID == 1 {
agencyUrl := beego.AppConfig.String("cveagency::url")
agencyReq := make(map[string]interface{}, 0)
agencyReq["CveNum"] = issueTmp.CveNum
agencyReq["PackName"] = issueTmp.OwnedComponent
agencyReq["PackVersion"] = issueTmp.OwnedVersion
if len(branchList) > 0 {
agencyReq["AffectBranch"] = strings.Join(branchList, " ")
} else {
agencyReq["AffectBranch"] = ""
}
agencyReq["IssueNumber"] = issueTmp.IssueNum
util.HTTPPostCom(agencyReq, agencyUrl)
}
return errx
}
func gitDelIssueProc(issueHook *models.IssuePayload, organizationID int8) error {
DelOrgIssue(issueHook, organizationID)
return nil
}
// SetIssueStateByReason sets the issue state based on the given reason.
func SetIssueStateByReason(issue *models.IssueTemplate, remoteStateName string) error {
state, err := GetIssueStateByAllReason(issue)
if err != nil {
return err
}
issue.StatusName = state
switch state {
case common.StateOpen:
issue.Status = 1
case common.StateProcessing:
issue.Status = 2
case common.StateSuspend:
issue.Status = 5
case common.StateClosed:
issue.Status = 3
}
// issue本身的状态和计算后的状态一致,则不做任何操作
stateNameMap := common.GetStateNameMap()
stateName, ok := stateNameMap[state]
if !ok || stateName == remoteStateName {
return nil
}
stateIdMap := common.GetStateIdMap()
stateId, ok := stateIdMap[state]
if !ok {
return errors.New("can not find state id")
}
return taskhandler.UpdateIssueStateToGitee(issue.IssueId, stateId)
}
// GetIssueStateByAllReason retrieves the issue state based on all reasons associated with the issue.
func GetIssueStateByAllReason(issue *models.IssueTemplate) (string, error) {
allState, err := StateByAllReason(issue)
if err != nil {
return "", err
}
return MaxPriorityState(allState), nil
}
// MaxPriorityState returns the state with the highest priority from the given list of states.
func MaxPriorityState(allState []string) string {
var maxPriority string
for _, v := range common.GetStatePriority() {
for _, state := range allState {
if v == state {
maxPriority = v
}
}
}
return maxPriority
}
// StateByAllReason return all reason from the given issue.
func StateByAllReason(issue *models.IssueTemplate) ([]string, error) {
var mergedPR []string
var err error
if issue.ContainsWillFix() {
if mergedPR, err = GetMergedPR(issue); err != nil {
return nil, err
}
}
split := strings.Split(issue.AnalysisVersion, ",")
var allState []string
for _, v := range split {
allState = append(allState, StateFromReason(v, mergedPR))
}
return allState, nil
}
// GetRelatedPR retrieves related pull requests for a given issue template.
func GetRelatedPR(issue *models.IssueTemplate) ([]gitee.PullRequest, error) {
token := beego.AppConfig.String("gitee::git_token")
endpoint := fmt.Sprintf("https://gitee.com/api/v5/repos/%v/issues/%v/pull_requests?access_token=%s&repo=%s",
issue.Owner, issue.IssueNum, token, issue.Repo,
)
req, err := http.NewRequest(http.MethodGet, endpoint, nil)
if err != nil {
return nil, err
}
const retryTimes = 3
cli := utils.NewHttpClient(retryTimes)
b, _, err := cli.Download(req)
if err != nil {
return nil, err
}
var prs []gitee.PullRequest
err = json.Unmarshal(b, &prs)
return prs, err
}
// GetMergedPR retrieves a list of merged pull requests related to the given issue.
func GetMergedPR(issue *models.IssueTemplate) ([]string, error) {
prs, err := GetRelatedPR(issue)
if err != nil {
return nil, err
}
var mergedPR []string
for _, pr := range prs {
if pr.State == "merged" {
mergedPR = append(mergedPR, pr.Base.Ref)
}
}
return mergedPR, nil
}
// StateFromReason determines the state of a pull request based on the branch and reason provided.
// It returns the state as a string. If the branch and reason are not valid or the reason is empty,
// it returns the common.StateOpen.
func StateFromReason(branchAndReason string, mergedPR []string) string {
const splitLen = 2
item := strings.Split(strings.ReplaceAll(branchAndReason, ":", ":"), ":")
if len(item) != splitLen || item[1] == "" {
return common.StateOpen
}
analysisStateMap := common.GetAnalysisStateMap()
if v, ok := analysisStateMap[item[1]]; ok {
return v
}
// 原因为正常修复的分支,需要看对应分支的PR是否合入来决定状态
mergedPRSets := sets.NewString(mergedPR...)
if mergedPRSets.Has(item[0]) {
return common.StateClosed
} else {
return common.StateProcessing
}
}
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。