<|start_filename|>cmd/worker/sast/process_test.go<|end_filename|>
package sast

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestIsBlacklisted(t *testing.T) {

	blackListedRepositories := []string{
		"OrganizationAccount/RepositoryName2",
		"OrganizationAccount/RepositoryName3",
		"AnotherOrganizationAccount/RepositoryName",
	}
	repo := "http://github.com/OrganizationAccount/RepositoryName2/"

	current := isBlacklisted(repo, blackListedRepositories)
	assert.True(t, current)

	repo = "http://github.com/AnotherOrganizationAccount/RepositoryName2/"

	current = isBlacklisted(repo, blackListedRepositories)
	assert.False(t, current)

	repo = "http://github.com/OrganizationAccount/RepositoryName/"

	current = isBlacklisted(repo, blackListedRepositories)
	assert.False(t, current)
}

func TestGetHash(t *testing.T) {
	actual := getHash("abc")
	expected := "a9993e364706816aba3e25717850c26c9cd0d89d"

	assert.Equal(t, expected, actual)

	actual = getHash("abcdefgh", "18ncyas8d", "a", "(*q/qwe")
	expected = "5a74a7163108aca1983083127fbc3f9e65ce3ffe"

	assert.Equal(t, expected, actual)
}


<|start_filename|>log/slack/slack.go<|end_filename|>
package slack

import (
	"bytes"
	"encoding/json"
	"fmt"
	"net/http"

	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/log"
)

func Send(repository, vulnerability, filename, values, id, issueURL string) {
	slackWebHook := config.Opts.SlackWebhook
	interactiveActions := getInteractiveActionsFields(id, issueURL)
	if slackWebHook != "" {
		values := map[string]interface{}{
			"text": ":warning: *Vulnerability found*",
			"attachments": []map[string]interface{}{
				map[string]interface{}{
					"color": "#AB3117",
					"fields": []map[string]interface{}{
						map[string]interface{}{
							"title": "Repository",
							"value": repository,
							"short": false,
						},
						map[string]interface{}{
							"title": "Vulnerability",
							"value": vulnerability,
							"short": false,
						},
						map[string]interface{}{
							"title": "File",
							"value": filename,
							"short": false,
						},
						map[string]interface{}{
							"title": "Values",
							"value": values,
							"short": false,
						},
					},
				},
				interactiveActions,
			},
		}
		jsonValue, _ := json.Marshal(values)
		resp, err := http.Post(slackWebHook, "application/json", bytes.NewBuffer(jsonValue))
		if err != nil {
			log.Error(err.Error(), nil)
		} else if resp.StatusCode != http.StatusOK {
			log.Error(fmt.Sprintf("cannot send message to slack [status %d]", resp.StatusCode), nil)
		}
	}
}

func getInteractiveActionsFields(id, issueURL string) map[string]interface{} {
	falsePositiveButton := map[string]string{
		"name":  "False Positive",
		"text":  "False Positive",
		"type":  "button",
		"value": "false_positive",
	}

	if issueURL != "" {
		return map[string]interface{}{
			"color": "#3AA3E3",
			"fields": []map[string]interface{}{
				map[string]interface{}{
					"title": "Issue",
					"value": issueURL,
					"short": false,
				},
			},
		}

	}
	return map[string]interface{}{
		"callback_id":     id,
		"title":           "Actions",
		"color":           "#3AA3E3",
		"attachment_type": "default",
		"actions": []map[string]string{
			map[string]string{
				"name":  "Open Issue",
				"text":  "Open Issue",
				"type":  "button",
				"value": "open_issue",
			},
			falsePositiveButton,
		},
	}
}


<|start_filename|>management/util.go<|end_filename|>
package management

import (
	"fmt"

	"github.com/ResultadosDigitais/x9/crypto"
)

func getID(params ...interface{}) string {
	payload := fmt.Sprintf("%v", params[0])
	for _, param := range params[1:len(params)] {
		payload = fmt.Sprintf("%v,%v", payload, param)
	}
	return crypto.SHA256(payload)
}


<|start_filename|>cmd/app/handler/router.go<|end_filename|>
package handler

import (
	"html/template"
	"io"

	"github.com/labstack/echo/v4"
)

type Handler struct{}

type Template struct {
	templates *template.Template
}

func (t *Template) Render(w io.Writer, name string, data interface{}, c echo.Context) error {
	return t.templates.ExecuteTemplate(w, name, data)
}

func InitHandler() Handler {
	return Handler{}
}

func InitTemplate() Template {
	return Template{
		templates: template.Must(template.ParseGlob("templates/*.html")),
	}
}


<|start_filename|>management/util_test.go<|end_filename|>
package management

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestGetID(t *testing.T) {

	expected := "5e825fe33b90934ae809cd79688f9f4da07b0a357b3062e3e892ea183125d511"

	resp := getID(1, true, "abc")

	assert.Equal(t, expected, resp)

}


<|start_filename|>db/postgres.go<|end_filename|>
package db

import (
	"database/sql"
	"fmt"

	"github.com/ResultadosDigitais/x9/config"
	_ "github.com/lib/pq"
)

var conn *sql.DB

type Postgres struct {
	Host         string `env:"POSTGRES_HOST,required"`
	Port         int    `env:"POSTGRES_PORT,required"`
	User         string `env:"POSTGRES_USER,required"`
	Password     string `env:"POSTGRES_PASSWORD,required"`
	DataBaseName string `env:"POSTGRES_DB,required"`
}

var dbconfig Postgres

func GetDB(dbconf config.DatabaseConfig) error {
	dbconfig = Postgres{
		Host:         dbconf.Host,
		Port:         dbconf.Port,
		User:         dbconf.User,
		Password:     <PASSWORD>,
		DataBaseName: dbconf.DataBaseName,
	}
	err := Connect()
	return err
}

func Connect() error {
	psqlInfo := fmt.Sprintf("host=%s port=%d user=%s "+
		"password=%s dbname=%s sslmode=disable "+
		"sslmode=verify-ca sslrootcert=/var/x9/server-ca.pem "+
		"sslkey=/var/x9/client-key.pem sslcert=/var/x9/client-cert.pem",
		dbconfig.Host, dbconfig.Port, dbconfig.User, dbconfig.Password, dbconfig.DataBaseName)
	db, err := sql.Open("postgres", psqlInfo)
	if err != nil {
		return err
	}

	err = db.Ping()
	if err != nil {
		return err
	}

	SetConn(db)
	return nil

}

func SetConn(dbconn *sql.DB) {
	conn = dbconn
}

func Query(query string, params ...interface{}) (*sql.Rows, error) {
	stmt, err := conn.Prepare(query)
	if err != nil {
		return nil, err
	}
	rows, err := stmt.Query(params...)
	return rows, err
}
func QueryRow(query string, params ...interface{}) (*sql.Row, error) {
	stmt, err := conn.Prepare(query)
	if err != nil {
		return nil, err
	}
	row := stmt.QueryRow(params...)
	return row, err
}

func Exec(query string, params ...interface{}) error {
	stmt, err := conn.Prepare(query)
	if err != nil {
		return err
	}
	_, err = stmt.Exec(params...)

	return err
}

func InitTables() error {
	createTableVuln := `CREATE TABLE IF NOT EXISTS vulnerabilities (
		id varchar(200) NOT NULL,
		name varchar(200) NOT NULL,
		repository varchar(255) NOT NULL,
		filename varchar(255) NOT NULL,
		tool varchar(255) NOT NULL,
		value text NOT NULL,
		false_positive boolean NOT NULL DEFAULT false,
		issue_url varchar(255),
		PRIMARY KEY(id)
	)`

	_, err := conn.Exec(createTableVuln)
	if err != nil {
		return err
	}

	return nil
}


<|start_filename|>cmd/app/handler/handlers.go<|end_filename|>
package handler

import (
	"net/http"

	"github.com/ResultadosDigitais/x9/cmd/app/auth"
	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/log"
	"github.com/ResultadosDigitais/x9/management"
	"github.com/gorilla/sessions"
	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/v4"
)

func (h *Handler) Login(c echo.Context) error {
	return c.Render(http.StatusOK, "login.html", nil)
}

func (h *Handler) OIDCAuth(c echo.Context) error {
	sess, err := session.Get("x9-session", c)
	sess.Options = &sessions.Options{
		Path:     "/",
		MaxAge:   3600,
		HttpOnly: true,
	}
	state := auth.GetState()
	sess.Values["state"] = state
	sess.Save(c.Request(), c.Response())
	authURL, err := auth.GetAuthCodeURL(state, nil)
	if err != nil {
		log.Error("Cannot get OIDC auth code url", map[string]interface{}{"error": err.Error()})
		return c.JSON(http.StatusInternalServerError, nil)
	}
	return c.Redirect(http.StatusFound, authURL)
}

func (h *Handler) OIDCCallback(c echo.Context) error {
	sess, _ := session.Get("x9-session", c)
	state := sess.Values["state"]
	if c.QueryParam("state") != state {
		msg := "Missing state param"
		log.Info(msg, nil)
		return c.JSON(http.StatusBadRequest, map[string]string{"error": msg})
	}
	rawIDToken, err := auth.GetRawIDToken(c.QueryParam("code"))
	if err != nil {
		log.Error("Cannot get raw id token from given code", map[string]interface{}{"error": err.Error()})
		return c.JSON(http.StatusBadRequest, nil)
	}
	_, err = auth.VerifyToken(rawIDToken)
	if err != nil {
		log.Info("Invalid token", map[string]interface{}{"error": err.Error()})
		return c.JSON(http.StatusBadRequest, nil)
	}

	sess.Values["token"] = rawIDToken
	sess.Save(c.Request(), c.Response())

	return c.Redirect(http.StatusFound, "/dashboard")
}

func (h *Handler) Dashboard(c echo.Context) error {
	data := struct {
		AppURL string
	}{config.AppOpts.AppURL}
	return c.Render(http.StatusOK, "dashboard.html", data)
}

func (h *Handler) GetVulnerabilities(c echo.Context) error {

	search := c.QueryParam("search")

	var err error
	var vulns []management.Vulnerability

	if search != "" {
		vulns, err = management.GetVulnerabilitiesByNameOrRepo(search)
	} else {
		vulns, err = management.GetVulnerabilities()
	}
	if err != nil {
		log.Error("Error on getting vulnerabilities", map[string]interface{}{"error": err.Error()})
		return c.JSON(http.StatusBadRequest, nil)
	}
	return c.JSON(http.StatusOK, map[string]interface{}{"rows": vulns, "total": len(vulns)})
}


<|start_filename|>management/vulnerability_test.go<|end_filename|>
package management

import (
	"database/sql"
	"database/sql/driver"
	"errors"
	"testing"

	"github.com/DATA-DOG/go-sqlmock"
	"github.com/ResultadosDigitais/x9/db"
	"github.com/stretchr/testify/assert"
)

var dbconn *sql.DB

func setupDB(query string, r [][]driver.Value, err error) {
	conn, mock, _ := sqlmock.New()
	dbconn = conn
	db.SetConn(conn)
	rows := sqlmock.NewRows([]string{
		"id",
		"internal_id",
		"name",
		"repository",
		"filename",
		"tool",
		"value",
		"false_positive",
		"issue_url"})

	for _, v := range r {
		rows = rows.AddRow(v...)
	}
	if err != nil {
		if r != nil {
			mock.ExpectPrepare(query).ExpectQuery().WillReturnError(err)
		} else {
			mock.ExpectPrepare(query).ExpectExec().WillReturnError(err)
		}
	} else {
		if r != nil {
			mock.ExpectPrepare(query).ExpectQuery().WillReturnRows(rows)
		} else {
			mock.ExpectPrepare(query).ExpectExec().WillReturnResult(sqlmock.NewResult(0, 1))
		}
	}
}

func shutdownDB() {
	dbconn.Close()
}

func TestGetVulnerabilityByID(t *testing.T) {
	setupDB(
		"^SELECT (.*) FROM (.*) WHERE id = (.*)$",
		[][]driver.Value{
			[]driver.Value{"123", "456", "Vuln1", "Repo", "file", "tool", "value", false, "url"},
		},
		nil,
	)
	v, err := GetVulnerabilityByID("123")
	shutdownDB()
	assert.NoError(t, err)
	expected := Vulnerability{
		ID:            "123",
		InternalID:    "456",
		Name:          "Vuln1",
		Repository:    "Repo",
		FileName:      "file",
		Tool:          "tool",
		Value:         "value",
		FalsePositive: false,
		IssueURL:      "url",
	}
	assert.EqualValues(t, expected, v)

}

func TestGetVulnerabilityByName(t *testing.T) {
	setupDB(
		"^SELECT (.*) FROM (.*) WHERE name = (.*)$",
		[][]driver.Value{
			[]driver.Value{"123", "456", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
			[]driver.Value{"789", "567", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
		},
		nil,
	)
	v, err := GetVulnerabilitiesByName("Vuln2")
	shutdownDB()
	assert.NoError(t, err)
	expected := []Vulnerability{
		Vulnerability{"123", "456", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
		Vulnerability{"789", "567", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
	}
	assert.EqualValues(t, expected, v)

}

func TestGetVulnerabilityByRepo(t *testing.T) {
	setupDB(
		"^SELECT (.*) FROM (.*) WHERE repository LIKE (.*)$",
		[][]driver.Value{
			[]driver.Value{"123", "456", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
			[]driver.Value{"789", "567", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
		},
		nil,
	)
	v, err := GetVulnerabilitiesByRepo("Vuln2")

	shutdownDB()
	assert.NoError(t, err)
	expected := []Vulnerability{
		Vulnerability{"123", "456", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
		Vulnerability{"789", "567", "Vuln2", "Repo", "file", "tool", "value", false, "url"},
	}
	assert.EqualValues(t, expected, v)

}

func TestInsertVulnerability(t *testing.T) {
	setupDB(
		"^INSERT INTO (.*) VALUES (.*)$",
		nil,
		nil,
	)
	v := Vulnerability{"123", "456", "Vuln2", "Repo", "file", "tool", "value", false, "url"}

	_, err := InsertVulnerability(v)

	shutdownDB()
	assert.NoError(t, err)
}

func TestInsertVulnerabilityShouldFail(t *testing.T) {
	setupDB(
		"^INSERT INTO (.*) VALUES (.*)$",
		nil,
		errors.New("DB error"),
	)
	v := Vulnerability{"123", "456", "Vuln2", "Repo", "file", "tool", "value", false, "url"}

	_, err := InsertVulnerability(v)

	shutdownDB()
	assert.Error(t, err)
}


<|start_filename|>crypto/hash.go<|end_filename|>
package crypto

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/hex"
	"fmt"
)

func SHA256(payload string) string {
	h := sha256.New()
	h.Write([]byte(payload))
	return hex.EncodeToString(h.Sum(nil))
}

func HMAC(data, secret string) string {
	h := hmac.New(sha256.New, []byte(secret))
	h.Write([]byte(data))
	return hex.EncodeToString(h.Sum(nil))
}

func ValidateHMAC(current, expected, prefix, secret string) bool {
	current = fmt.Sprintf("%s=%s", prefix, HMAC(current, secret))
	return current == expected
}


<|start_filename|>cmd/worker/sast/match_test.go<|end_filename|>
package sast

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func getConfig() Config {
	return Config{
		BlacklistedExtensions:        []string{".ext"},
		BlacklistedPaths:             []string{"path/anotherpath"},
		BlacklistedEntropyExtensions: []string{".abc", ".def"},
	}
}

func TestIsSkippableFile(t *testing.T) {
	config := getConfig()
	path := "abc/path/anotherpath/file.abc"
	assert.True(t, IsSkippableFile(config, path))

	path = "abc/def/ghi.ext"
	assert.True(t, IsSkippableFile(config, path))

	path = "abc/def.extension"
	assert.False(t, IsSkippableFile(config, path))
}

func TestCanCheckEntropy(t *testing.T) {
	config := getConfig()

	match := MatchFile{
		Path:      "/xyz",
		Filename:  "jkl",
		Extension: ".mno",
	}

	assert.True(t, match.CanCheckEntropy(config))

	match.Extension = ".abc"
	assert.False(t, match.CanCheckEntropy(config))

}


<|start_filename|>cmd/worker/router/router.go<|end_filename|>
package router

import (
	"github.com/ResultadosDigitais/x9/cmd/worker/git"
	"github.com/google/go-github/github"
	"github.com/labstack/echo"
)

type Router interface {
	Healthcheck(c echo.Context) error
	Event(c echo.Context) error
}

type Handler struct {
	Process chan *github.PullRequestEvent
	Session *git.GithubSession
}


<|start_filename|>cmd/app/main.go<|end_filename|>
package main

import (
	"github.com/ResultadosDigitais/x9/db"
	"github.com/gorilla/sessions"

	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/v4"

	"github.com/ResultadosDigitais/x9/cmd/app/auth"
	"github.com/ResultadosDigitais/x9/cmd/app/handler"
	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/log"
)

func main() {

	log.Init()
	log.Info("X9 app started...", nil)

	if err := config.ParseAppConfig(); err != nil {
		log.Fatal("Error on parsing config", map[string]interface{}{"error": err.Error()})
	}

	if err := db.GetDB(config.AppOpts.DatabaseConfig); err != nil {
		log.Fatal("Database connection error", map[string]interface{}{"error": err.Error()})
	}
	if err := auth.InitOIDC(); err != nil {
		log.Fatal("OIDC configuration failed", map[string]interface{}{"error": err.Error()})

	}
	t := handler.InitTemplate()
	h := handler.InitHandler()
	e := echo.New()
	e.Renderer = &t
	e.Use(session.Middleware(sessions.NewCookieStore([]byte(config.AppOpts.ApplicationSecretKey))))
	e.Static("/static", "templates/css")
	e.GET("/login", h.Login)
	e.GET("/auth/google/callback", h.OIDCCallback)
	e.GET("/auth", h.OIDCAuth)
	e.GET("/dashboard", h.Dashboard, handler.AuthMiddleware)
	e.GET("/vuln", h.GetVulnerabilities, handler.AuthMiddleware)

	e.Logger.Fatal(e.Start(":3000"))

}


<|start_filename|>cmd/worker/sast/leaks_test.go<|end_filename|>
package sast

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestObfuscate(t *testing.T) {
	str := "testing_obfuscate"
	expected := "testing_obf******"
	actual := obfuscate(str)

	assert.Equal(t, expected, actual)
}


<|start_filename|>cmd/worker/sast/process.go<|end_filename|>
package sast

import (
	"crypto/sha1"
	"encoding/hex"
	"errors"
	"fmt"
	"os"
	"path/filepath"
	"regexp"
	"strings"
	"time"

	"github.com/ResultadosDigitais/x9/cmd/worker/git"
	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/log"
	"github.com/google/go-github/github"
)

type ProcessWorker struct {
	Session git.GithubSession
	Leaks   Leaks
	Events  chan *github.PullRequestEvent
}

func (pw *ProcessWorker) InitWorkers(w int) {
	for i := 0; i < w; i++ {
		go pw.ProcessEvent()
	}
}

func (pw *ProcessWorker) ProcessEvent() {
	for e := range pw.Events {
		repository, err := pw.Session.GetRepository(*e.GetPullRequest().GetHead().GetRepo().ID)
		if err != nil {
			log.Error(fmt.Sprintf("Error getting repository info: %s", e.GetRepo().URL), map[string]interface{}{"error": err.Error()})
			continue
		}
		url := repository.GetCloneURL()
		if isBlacklisted(url, config.GetBlacklistedRepositories()) {
			continue
		}
		branch := fmt.Sprintf("refs/heads/%s", *e.GetPullRequest().GetHead().Ref)
		dir := getDir(url)
		if _, err := pw.Session.CloneRepository(url, branch, dir); err != nil {
			log.Error(fmt.Sprintf("Error cloning repository: %s", url), map[string]interface{}{"error": err.Error()})
			continue
		}

		pw.Leaks.Test(url, dir)
		os.RemoveAll(dir)
	}
}

func getUserAndRepo(label string) (string, string, error) {
	userAndRepo := strings.Split(label, ":")
	if len(userAndRepo) != 2 {
		return "", "", errors.New(fmt.Sprintf("Not a valid head label format: %s", label))
	}
	return userAndRepo[0], userAndRepo[1], nil
}

func isBlacklisted(repo string, blackListedRepositories []string) bool {
	for _, blackListedRepo := range blackListedRepositories {
		if match, err := regexp.MatchString(`^.*(/`+blackListedRepo+`/).*$`, repo); err != nil {
			log.Error("Regexp error compile", map[string]interface{}{"error": err.Error()})
		} else if match == true {
			return true
		}
	}
	return false

}

func getDir(url string) string {
	folderName := getHash(url, time.Now().String())
	return getTempDir(os.TempDir(), folderName)

}

func getTempDir(tempDir, suffix string) string {
	dir := filepath.Join(tempDir, suffix)

	if _, err := os.Stat(dir); os.IsNotExist(err) {
		os.MkdirAll(dir, os.ModePerm)
	} else {
		os.RemoveAll(dir)
	}

	return dir
}

// getHash receives one or more strings and returns
// a sha1 hash of the concatenation of all of them
func getHash(values ...string) string {
	str := values[0]
	for i := 1; i < len(values); i++ {
		str = fmt.Sprintf("%s%s", str, values[i])
	}
	hashString := sha1.New()
	hashString.Write([]byte(str))
	return hex.EncodeToString(hashString.Sum(nil))
}


<|start_filename|>cmd/worker/main.go<|end_filename|>
package main

import (
	"github.com/ResultadosDigitais/x9/cmd/worker/git"
	"github.com/ResultadosDigitais/x9/db"

	"github.com/ResultadosDigitais/x9/cmd/worker/router"
	"github.com/ResultadosDigitais/x9/cmd/worker/sast"
	"github.com/google/go-github/github"
	"github.com/labstack/echo/v4"

	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/log"
)

func main() {
	log.Init()
	log.Info("X9 started...", nil)

	if err := config.ParseConfig(); err != nil {
		log.Fatal("Error on parsing config", map[string]interface{}{"error": err.Error()})
	}

	if err := db.GetDB(config.Opts.DatabaseConfig); err != nil {
		log.Fatal("Database connection error", map[string]interface{}{"error": err.Error()})
	}
	db.InitTables()

	githubSession := git.GithubSession{}
	if err := githubSession.InitClient(); err != nil {
		log.Fatal(err.Error(), nil)
	}

	leaks := sast.Leaks{}
	if err := leaks.GetLeaksConfig(); err != nil {
		log.Fatal(err.Error(), nil)

	}

	eventsChannel := make(chan *github.PullRequestEvent)
	processWorker := sast.ProcessWorker{
		Session: githubSession,
		Leaks:   leaks,
		Events:  eventsChannel,
	}
	processWorker.InitWorkers(*config.Opts.Threads)

	handler := router.Handler{
		Process: eventsChannel,
		Session: &githubSession,
	}
	e := echo.New()

	e.GET("/healthcheck", handler.HealthCheck)
	e.POST("/events", handler.Event)
	e.POST("/action", handler.Action)

	e.Logger.Fatal(e.Start(":3000"))

}


<|start_filename|>cmd/app/handler/middleware.go<|end_filename|>
package handler

import (
	"fmt"
	"net/http"

	"github.com/ResultadosDigitais/x9/cmd/app/auth"
	"github.com/ResultadosDigitais/x9/log"
	"github.com/labstack/echo-contrib/session"
	"github.com/labstack/echo/v4"
)

func AuthMiddleware(next echo.HandlerFunc) echo.HandlerFunc {
	return func(c echo.Context) error {
		sess, err := session.Get("x9-session", c)
		if err != nil {
			log.Error("Error on getting session", map[string]interface{}{"error": err.Error()})
			return c.JSON(http.StatusInternalServerError, nil)
		}
		token := sess.Values["token"]
		if token != nil {
			oidcToken, err := auth.VerifyToken(token.(string))
			if err == nil {
				userInfo := struct {
					Email string `json:"email"`
					Name  string `json:"name"`
				}{"", ""}
				oidcToken.Claims(&userInfo)
				log.Info(fmt.Sprintf("User %s requested %s", userInfo.Email, c.Path()), nil)
				c.Set("email", userInfo.Email)
				c.Set("name", userInfo.Name)
				if err := next(c); err != nil {
					log.Error("Error handling function", map[string]interface{}{"error": err.Error()})
					return c.JSON(http.StatusInternalServerError, nil)
				}
				return nil
			}
			log.Error("Error on verifying user token", map[string]interface{}{"error": err.Error()})

		} else {
			log.Error("Missing token in user session", nil)
		}

		return c.Redirect(http.StatusFound, "/login")
	}
}


<|start_filename|>config/database.go<|end_filename|>
package config

import "strconv"

type DatabaseConfig struct {
	Host         string `env:"POSTGRES_HOST,required"`
	Port         int    `env:"POSTGRES_PORT,required"`
	User         string `env:"POSTGRES_USER,required"`
	Password     string `env:"POSTGRES_PASSWORD,required"`
	DataBaseName string `env:"POSTGRES_DB,required"`
}

func ParseDatabaseConfig() (DatabaseConfig, error) {
	dbconfig := DatabaseConfig{}
	var err error
	if dbconfig.Host, err = getEnv("POSTGRES_HOST"); err != nil {
		return dbconfig, err
	}
	stringPort, err := getEnv("POSTGRES_PORT")
	if err != nil {
		return dbconfig, err
	}
	if dbconfig.Port, err = strconv.Atoi(stringPort); err != nil {
		return dbconfig, err
	}
	if dbconfig.User, err = getEnv("POSTGRES_USER"); err != nil {
		return dbconfig, err
	}
	if dbconfig.Password, err = getEnv("POSTGRES_PASSWORD"); err != nil {
		return dbconfig, err
	}
	if dbconfig.DataBaseName, err = getEnv("POSTGRES_DB"); err != nil {
		return dbconfig, err
	}
	return dbconfig, nil
}


<|start_filename|>cmd/worker/sast/config.go<|end_filename|>
package sast

import (
	"io/ioutil"
	"os"
	"path"

	"gopkg.in/yaml.v3"
)

// Config has the main analyzer configuration
type Config struct {
	BlacklistedExtensions        []string          `yaml:"blacklisted_extensions"`
	BlacklistedPaths             []string          `yaml:"blacklisted_paths"`
	BlacklistedEntropyExtensions []string          `yaml:"blacklisted_entropy_extensions"`
	Signatures                   []ConfigSignature `yaml:"signatures"`
}

//ConfigSignature stores the match signature configuration
type ConfigSignature struct {
	Name     string `yaml:"name"`
	Part     string `yaml:"part"`
	Match    string `yaml:"match,omitempty"`
	Regex    string `yaml:"regex,omitempty"`
	Verifier string `yaml:"verifier,omitempty"`
}

//ParseConfig reads the configuration from the config.yaml file and
func ParseConfig() (Config, error) {
	config := Config{}

	dir, _ := os.Getwd()
	data, err := ioutil.ReadFile(path.Join(dir, "sast/config.yaml"))
	if err != nil {
		return config, err
	}

	err = yaml.Unmarshal(data, &config)
	if err != nil {
		return config, err
	}

	return config, nil
}

//UnmarshalYAML gets the fields from  a yaml file
func (c *Config) UnmarshalYAML(unmarshal func(interface{}) error) error {
	*c = Config{}
	type plain Config
	err := unmarshal((*plain)(c))

	if err != nil {
		return err
	}

	return nil
}


<|start_filename|>cmd/worker/actions/actions.go<|end_filename|>
package actions

import (
	"encoding/json"
	"errors"
	"fmt"
	"regexp"
	"strings"

	"github.com/ResultadosDigitais/x9/cmd/worker/git"
	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/log"
	"github.com/ResultadosDigitais/x9/management"
)

type SlackAction struct {
	Type            string          `json:"type"`
	Actions         []Action        `json:"actions"`
	CallbackID      string          `json:"callback_id"`
	Team            Team            `json:"team"`
	Channel         Channel         `json:"channel"`
	User            User            `json:"user"`
	ActionTS        string          `json:"action_ts"`
	MessageTS       string          `json:"message_ts"`
	AttachmentID    string          `json:"attachment_id"`
	Token           string          `json:"token"`
	OriginalMessage json.RawMessage `json:"original_message"`
	ResponseURL     string          `json:"response_url"`
	TriggerID       string          `json:"trigger_id"`
}

type Action struct {
	Name  string `json:"name"`
	Value string `json:"value"`
	Type  string `json:"type"`
}

type Team struct {
	ID     string `json:"id"`
	Domain string `json:"domain"`
}
type Channel struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}
type User struct {
	ID   string `json:"id"`
	Name string `json:"name"`
}

func ProcessAction(body string, gs *git.GithubSession) error {
	var action SlackAction
	if err := json.Unmarshal([]byte(body), &action); err != nil {
		log.Error("Error parsing json", map[string]interface{}{"error": err.Error()})
	}

	if action.Type != "interactive_message" {
		msg := fmt.Sprintf("Unknow action type: %s", action.Type)
		log.Warn(msg, nil)
		return errors.New(msg)
	}
	if !userCanPerformAction(action.User.Name) {
		msg := fmt.Sprintf("Permission denied: %s", action.User.Name)
		log.Warn(msg, nil)
		return errors.New(msg)
	}
	switch action.Actions[0].Value {
	case "open_issue":
		vuln, err := management.GetVulnerabilityByID(action.CallbackID)
		if err != nil {
			log.Error("Error on getting vulnerability", map[string]interface{}{"error": err.Error()})
			return err
		}
		if vuln.IssueURL != "" {
			return nil
		}
		title := getIssueTitle(vuln.Name, vuln.FileName)
		body := getIssueBody(vuln.Name, vuln.Value, vuln.FileName)
		labels := []string{
			"X9",
			"Security",
		}
		owner, repo := getRepoInfo(vuln.Repository)
		issueURL, err := gs.OpenIssue(owner, repo, title, body, labels)
		if err != nil {
			log.Error("Error on opening issue", map[string]interface{}{"error": err.Error()})
		} else {
			if err := management.SetIssueURL(vuln.ID, issueURL); err != nil {
				log.Error("Error on updating issue URL", map[string]interface{}{"error": err.Error()})
			}
		}

		return err
	case "false_positive":
		if !isUserAllowed(action.User.Name) {
			log.Error(fmt.Sprintf("User %s not allowed to perform action: set as false positive", action.User.Name), nil)
		}
		err := management.SetAsFalsePositive(action.CallbackID)
		if err != nil {
			log.Error("Error on setting issue as false positive", map[string]interface{}{"error": err.Error()})
		}
		log.Info(fmt.Sprintf("Vunerability %s set as false positive by %s", action.CallbackID, action.User.Name), nil)
		return err
	}

	return nil
}

func isUserAllowed(user string) bool {
	for _, value := range config.Opts.SlackActionsUsersAllowed {
		if user == value {
			return true
		}
	}
	return false
}
func getRepoInfo(url string) (string, string) {
	r := regexp.MustCompile(`((https://([a-z]+)\.com/)|(\.git$))`)
	ownerAndRepo := r.ReplaceAllString(url, "")
	info := strings.Split(ownerAndRepo, "/")
	return info[0], info[1]
}

func getIssueTitle(name, filename string) string {
	return fmt.Sprintf(
		"[Vulnerability] Sensitive data: %s in %s",
		name,
		filename,
	)
}

func getIssueBody(name, values, filename string) string {
	return fmt.Sprintf(
		`# X9 Vulnerability Report
### Description
- **Vulnerability**: %s
- **Values**: %s
- **Filename**: %s`,
		name,
		values,
		filename,
	)
}

func userCanPerformAction(user string) bool {
	for _, u := range config.Opts.SlackActionsUsersAllowed {
		if u == user {
			return true
		}
	}
	return false
}


<|start_filename|>management/vulnerability.go<|end_filename|>
package management

import (
	"database/sql"
	"fmt"

	"github.com/ResultadosDigitais/x9/db"
)

// Vulnerability contains the database fields
// of vulnerabilities table
type Vulnerability struct {
	ID            string `json:"id"`
	Name          string `json:"name"`
	Repository    string `json:"repository"`
	FileName      string `json:"filename"`
	Tool          string `json:"tool"`
	Value         string `json:"value"`
	FalsePositive bool   `json:"false_positive"`
	IssueURL      string `json:"issue_url"`
}

// InsertVulnerability inserts a new vulnerability
// if it does not exists
func InsertVulnerability(v Vulnerability) (Vulnerability, error) {
	v.ID = getID(v.Name, v.Repository, v.Tool, v.Value, v.FileName)

	if vuln, err := GetVulnerabilityByID(v.ID); err == nil {
		return vuln, nil
	} else if err != sql.ErrNoRows {
		return Vulnerability{}, err
	}

	sql := `INSERT INTO vulnerabilities VALUES ($1, $2, $3, $4, $5, $6, $7, $8)`

	return v, db.Exec(sql, v.ID, v.Name, v.Repository, v.FileName, v.Tool, v.Value, v.FalsePositive, v.IssueURL)

}

// GetVulnerabilities gets all vulnerabilities
func GetVulnerabilities() ([]Vulnerability, error) {
	sql := `SELECT * FROM vulnerabilities 
			ORDER BY false_positive desc, repository asc, name asc`

	row, err := db.Query(sql)
	if err != nil {
		return nil, err
	}
	return processRows(row)

}

// GetVulnerabilityByID search for a single vulnerability
// that maches with the id
func GetVulnerabilityByID(id string) (Vulnerability, error) {
	sql := `SELECT * FROM vulnerabilities
			  WHERE id = $1`

	row, err := db.QueryRow(sql, id)
	if err != nil {
		return Vulnerability{}, err
	}
	return processRow(row)

}

func GetVulnerabilitiesByNameOrRepo(search string) ([]Vulnerability, error) {
	sql := `SELECT * FROM vulnerabilities
			WHERE LOWER(name) LIKE LOWER($1)
			OR LOWER(repository) LIKE LOWER($1)
			ORDER BY false_positive desc`

	search = fmt.Sprintf("%%%s%%", search)
	rows, err := db.Query(sql, search)
	if err != nil {
		return nil, err
	}
	return processRows(rows)
}

// SetAsFalsePositive sets the Vulnerability as a
// false positive
func SetAsFalsePositive(id string) error {
	sql := `UPDATE vulnerabilities
			SET false_positive = TRUE
			WHERE id = $1`
	return db.Exec(sql, id)
}

func SetIssueURL(id, url string) error {
	sql := `UPDATE vulnerabilities
			SET issue_url = $1
			WHERE id = $2
			AND issue_url = $3`
	return db.Exec(sql, url, id, "")
}

func processRow(row *sql.Row) (Vulnerability, error) {
	v := Vulnerability{}

	err := row.Scan(
		&v.ID,
		&v.Name,
		&v.Repository,
		&v.FileName,
		&v.Tool,
		&v.Value,
		&v.FalsePositive,
		&v.IssueURL,
	)

	return v, err
}

func processRows(rows *sql.Rows) ([]Vulnerability, error) {
	vs := []Vulnerability{}

	for rows.Next() {
		var v Vulnerability
		err := rows.Scan(
			&v.ID,
			&v.Name,
			&v.Repository,
			&v.FileName,
			&v.Tool,
			&v.Value,
			&v.FalsePositive,
			&v.IssueURL,
		)
		if err != nil {
			return nil, err
		}
		vs = append(vs, v)
	}
	return vs, nil
}


<|start_filename|>log/log.go<|end_filename|>
package log

import (
	"os"
	"runtime"

	"github.com/sirupsen/logrus"
	log "github.com/sirupsen/logrus"
)

type Logger struct {
	Logger *logrus.Logger
}

var logger Logger

func Init() {
	level := logrus.InfoLevel
	if os.Getenv("DEBUG_MODE") == "1" {
		level = logrus.DebugLevel
	}
	logger = Logger{
		Logger: &logrus.Logger{
			Out:       os.Stdout,
			Hooks:     make(logrus.LevelHooks),
			Level:     level,
			Formatter: &logrus.JSONFormatter{},
		},
	}
}

func Fatal(msg string, extraFields map[string]interface{}) {
	logger.Logger.WithFields(getFields(extraFields)).Fatal(msg)
}

func Error(msg string, extraFields map[string]interface{}) {
	logger.Logger.WithFields(getFields(extraFields)).Error(msg)
}

func Warn(msg string, extraFields map[string]interface{}) {
	logger.Logger.WithFields(getFields(extraFields)).Warn(msg)
}

func Info(msg string, extraFields map[string]interface{}) {
	logger.Logger.WithFields(getFields(extraFields)).Info(msg)

}

func Debug(msg string, extraFields map[string]interface{}) {
	logger.Logger.WithFields(getFields(extraFields)).Debug(msg)
}

func getFields(extraFields map[string]interface{}) logrus.Fields {
	programCounter, file, line, _ := runtime.Caller(2)
	function := runtime.FuncForPC(programCounter)
	fields := log.Fields{
		"file":     file,
		"line":     line,
		"function": function.Name(),
	}
	for k, v := range extraFields {
		fields[k] = v
	}
	return fields
}


<|start_filename|>cmd/app/templates/css/login.css<|end_filename|>
.jumbotron h1 {
    font-family: 'Roboto',sans-serif;
    font-size: 52px;
    font-weight: 900;
}
.jumbotron h2 {
    font-family: 'Roboto', sans-serif;
    font-weight: 700;
    font-size: 40px;
}

.jumbotron p {
    color:darkgray;
}

.subtitle {
    color: whitesmoke !important;
}
.link {
    color: white;
}

.social {
    color: whitesmoke !important;
}

.mt-6 {
    margin-top: 6rem;
}

.small-title {
    font-family: 'Roboto',sans-serif;
    font-size: 30px;
    font-weight: 900;
}

.bg-very-dark {
    background-color: #161616;
}

<|start_filename|>cmd/app/auth/auth.go<|end_filename|>
package auth

import (
	"context"
	"crypto/rand"
	"encoding/base64"
	"errors"

	"github.com/ResultadosDigitais/x9/config"
	"github.com/coreos/go-oidc"
	"golang.org/x/oauth2"
)

var oauth2Config oauth2.Config
var oidcVerifier *oidc.IDTokenVerifier
var ctx context.Context

func InitOIDC() error {
	ctx = context.Background()
	provider, err := oidc.NewProvider(ctx, config.AppOpts.OIDC.ServiceURL)
	if err != nil {
		return err
	}
	conf := &oidc.Config{
		ClientID: config.AppOpts.OIDC.ClientID,
	}
	oidcVerifier = provider.Verifier(conf)
	oauth2Config = oauth2.Config{
		ClientID:     config.AppOpts.OIDC.ClientID,
		ClientSecret: config.AppOpts.OIDC.ClientSecret,
		Endpoint:     provider.Endpoint(),
		RedirectURL:  config.AppOpts.OIDC.RedirectURL,
		Scopes:       []string{oidc.ScopeOpenID, "profile", "email"},
	}
	return nil
}

func GetAuthCodeURL(state string, extraParams map[string]string) (string, error) {
	return oauth2Config.AuthCodeURL(state, getExtraAuthOptions(extraParams)...), nil
}

func GetRawIDToken(code string) (string, error) {
	oauth2Token, err := oauth2Config.Exchange(ctx, code)
	if err != nil {
		return "", err
	}
	rawIDToken, ok := oauth2Token.Extra("id_token").(string)
	if !ok {
		return "", errors.New("Missing id_token")
	}
	return rawIDToken, nil
}

func VerifyToken(rawIDToken string) (*oidc.IDToken, error) {
	idToken, err := oidcVerifier.Verify(ctx, rawIDToken)
	if err != nil {
		return nil, err
	}
	return idToken, nil
}

func getExtraAuthOptions(extraParams map[string]string) []oauth2.AuthCodeOption {
	aco := []oauth2.AuthCodeOption{}
	for k, v := range extraParams {
		aco = append(aco, oauth2.SetAuthURLParam(k, v))
	}
	return aco
}

func GetState() string {
	b := make([]byte, 16)
	rand.Read(b)
	encoded := base64.StdEncoding.EncodeToString(b)

	return encoded

}


<|start_filename|>config/worker.go<|end_filename|>
package config

import (
	"errors"
	"fmt"
	"io/ioutil"
	"os"
	"path"
	"strings"

	"gopkg.in/yaml.v2"
)

const (
	Name    = "x9"
	Version = "0.1"
)

var (
	Opts = Options{}
)

type Options struct {
	Threads                  *int     `yaml:"num_threads"`
	MaximumRepositorySize    *int     `yaml:"maximum_repository_size"`
	MaximumFileSize          *int     `yaml:"maximum_file_size"`
	CloneRepositoryTimeout   *int     `yaml:"clone_repository_timeout"`
	BlackListedRepositories  []string `yaml:"blacklisted_repositories"`
	SlackActionsUsersAllowed []string
	DatabaseConfig           DatabaseConfig
	SlackWebhook             string
	SlackSecretKey           string
	GithubSecretWebhook      string
	GithubAccountUser        string
	GithubAccountToken       string
}

func ParseConfig() error {
	dir, _ := os.Getwd()
	data, err := ioutil.ReadFile(path.Join(dir, "config.yaml"))
	if err != nil {
		return err
	}
	Opts.SlackActionsUsersAllowed = getSlackUsersAllowed()
	if Opts.DatabaseConfig, err = ParseDatabaseConfig(); err != nil {
		return err
	}
	Opts.SlackWebhook, _ = getEnv("SLACK_WEBHOOK")
	Opts.SlackSecretKey, _ = getEnv("SLACK_SECRET_KEY")

	if Opts.GithubSecretWebhook, err = getEnv("GITHUB_SECRET_WEBHOOK"); err != nil {
		return err
	}
	if Opts.GithubAccountUser, err = getEnv("GITHUB_ACCOUNT_USER"); err != nil {
		return err
	}
	if Opts.GithubAccountToken, err = getEnv("GITHUB_ACCOUNT_TOKEN"); err != nil {
		return err
	}
	err = yaml.Unmarshal(data, &Opts)
	return err
}

func getEnv(envName string) (string, error) {
	env, ok := os.LookupEnv(envName)
	if !ok {
		return env, errors.New(fmt.Sprintf("Missing environment variable: %s", envName))
	}
	return env, nil
}
func getSlackUsersAllowed() []string {
	if env, ok := os.LookupEnv("SLACK_USERS_ALLOWED"); ok {
		return strings.Split(env, ",")
	}
	return nil
}

func GetBlacklistedRepositories() []string {
	return Opts.BlackListedRepositories
}


<|start_filename|>config/oidc.go<|end_filename|>
package config

import "fmt"

type OIDC struct {
	ClientID     string
	ClientSecret string
	RedirectURL  string
	ServiceURL   string
}

func ParseOIDCConfig() (OIDC, error) {
	oidc := OIDC{}
	var err error
	if oidc.ClientID, err = getEnv("OIDC_CLIENT_ID"); err != nil {
		return oidc, err
	}
	if oidc.ServiceURL, err = getEnv("OIDC_SERVICE_URL"); err != nil {
		return oidc, err
	}
	if oidc.ClientSecret, err = getEnv("OIDC_CLIENT_SECRET"); err != nil {
		return oidc, err
	}
	oidc.RedirectURL = fmt.Sprintf("%s/auth/google/callback", AppOpts.AppURL)

	return oidc, nil
}


<|start_filename|>config/app.go<|end_filename|>
package config

type AppConfig struct {
	ApplicationSecretKey string
	DatabaseConfig       DatabaseConfig
	AppURL               string
	OIDC                 OIDC
}

var AppOpts AppConfig

func ParseAppConfig() error {
	var err error

	if AppOpts.DatabaseConfig, err = ParseDatabaseConfig(); err != nil {
		return err
	}
	if AppOpts.ApplicationSecretKey, err = getEnv("APP_SECRET_KEY"); err != nil {
		return err
	}
	if AppOpts.AppURL, err = getEnv("APP_URL"); err != nil {
		return err
	}
	if AppOpts.OIDC, err = ParseOIDCConfig(); err != nil {
		return err
	}

	return nil
}


<|start_filename|>cmd/worker/sast/leaks.go<|end_filename|>
package sast

import (
	"bufio"
	"bytes"
	"fmt"
	"math"
	"os"
	"strings"

	"github.com/ResultadosDigitais/x9/management"

	"github.com/ResultadosDigitais/x9/log"

	"github.com/ResultadosDigitais/x9/log/slack"
)

type Leaks struct {
	Config     Config
	Signatures []Signature
}

func (l *Leaks) Test(url, dir string) {
	log.Info(fmt.Sprintf("Testing repository %s", url), nil)
	for _, file := range GetMatchingFiles(dir, l.Config) {
		relativeFileName := strings.Replace(file.Path, os.TempDir(), "", -1)
		relativeFileName = strings.SplitAfterN(relativeFileName, string(os.PathSeparator), 3)[2]

		for _, signature := range l.Signatures {
			if matched, part := signature.Match(file); matched {

				if part == PartContents {
					l.processMatches(file, signature, url, relativeFileName)
				} else {
					fields := map[string]interface{}{
						"repo": url,
						"vuln": signature.Name(),
						"file": relativeFileName,
					}
					go insertVulnerability(url, signature.Name(), relativeFileName, "N/A")

					log.Info("Vulnerability found", fields)

					l.checkEntropy(file, url, relativeFileName)
				}
			}
		}
	}
}

func (l *Leaks) GetLeaksConfig() error {

	config, err := ParseConfig()
	if err != nil {
		return err
	}
	l.Config = config
	l.Signatures = GetSignatures(l.Config)

	return nil

}

func (l *Leaks) processMatches(file MatchFile, signature Signature, url, relativeFileName string) {
	if matches := signature.GetContentsMatches(file); matches != nil {
		for i, _ := range matches {
			matches[i] = obfuscate(matches[i])
		}
		count := len(matches)
		m := strings.Join(matches, ", ")

		fields := map[string]interface{}{
			"repo":    url,
			"matches": count,
			"vuln":    signature.Name(),
			"file":    relativeFileName,
			"values":  m,
		}
		go insertVulnerability(url, signature.Name(), relativeFileName, m)
		log.Info("Vulnerability found", fields)
	}
}
func (l *Leaks) checkEntropy(file MatchFile, url, relativeFileName string) {
	if file.CanCheckEntropy(l.Config) {
		scanner := bufio.NewScanner(bytes.NewReader(file.Contents))

		for scanner.Scan() {
			line := scanner.Text()

			if len(line) > 6 && len(line) < 100 {
				entropy := getEntropy(scanner.Text())

				if entropy >= 5 {
					fields := map[string]interface{}{
						"repo":    url,
						"vuln":    "Potential secret",
						"file":    relativeFileName,
						"matches": 1,
						"values":  scanner.Text(),
					}
					go insertVulnerability(url, "Potential secret", relativeFileName, scanner.Text())
					log.Info("Vulnerability found", fields)
				}
			}
		}
	}
}

func getEntropy(data string) (entropy float64) {
	if data == "" {
		return 0
	}

	for i := 0; i < 256; i++ {
		px := float64(strings.Count(data, string(byte(i)))) / float64(len(data))
		if px > 0 {
			entropy += -px * math.Log2(px)
		}
	}

	return entropy
}
func getVulnerabilityStructure(url, name, filename, values string) management.Vulnerability {
	return management.Vulnerability{
		Name:       name,
		Repository: url,
		FileName:   filename,
		Value:      values,
		Tool:       "X9 - Leak",
	}
}

// Obfuscate changes the last 1/3 string characters by *'s
func obfuscate(text string) string {
	size := len(text)
	return text[0:2*size/3] + strings.Repeat("*", len(text)-2*size/3)
}

func insertVulnerability(repository, vulnerability, fileName, values string) {
	vuln := getVulnerabilityStructure(repository, vulnerability, fileName, values)
	vuln, err := management.InsertVulnerability(vuln)
	if err != nil {
		log.Error("Database error", map[string]interface{}{"error": err.Error()})
	}
	if !vuln.FalsePositive {
		slack.Send(repository, vulnerability, fileName, values, vuln.ID, vuln.IssueURL)
	}
}


<|start_filename|>cmd/worker/router/events.go<|end_filename|>
package router

import (
	"fmt"
	"io/ioutil"
	"net/http"
	"net/url"

	"github.com/ResultadosDigitais/x9/cmd/worker/actions"
	"github.com/ResultadosDigitais/x9/config"
	"github.com/ResultadosDigitais/x9/crypto"
	"github.com/ResultadosDigitais/x9/log"

	"github.com/google/go-github/github"
	"github.com/labstack/echo/v4"
)

// HealthCheck returns a status code 200
func (h *Handler) HealthCheck(c echo.Context) error {
	return c.String(http.StatusOK, "WORKING!")
}

// Event receives an event request, check its authencity and process it
func (h *Handler) Event(c echo.Context) error {
	payload, err := github.ValidatePayload(c.Request(), []byte(config.Opts.GithubSecretWebhook))
	if err != nil {
		log.Error("Request error", map[string]interface{}{"error": err.Error()})
		return c.NoContent(http.StatusForbidden)
	}

	event, err := github.ParseWebHook(github.WebHookType(c.Request()), payload)
	if err != nil {
		log.Error("Request error", map[string]interface{}{"error": err.Error()})
		return c.NoContent(http.StatusBadRequest)
	}
	src := c.Request().Header.Get("X-Forward-For")
	switch event := event.(type) {
	case *github.PullRequestEvent:
		if isX9Action(*event.Action) {
			log.Info(fmt.Sprintf("Event received: %s from repository %s", *event.Action, *event.GetRepo().FullName), map[string]interface{}{
				"src_ip": src,
			})
			h.Process <- event
		}

	default:
		log.Warn(fmt.Sprintf("Unexpected event received: %s", event), map[string]interface{}{
			"src_ip": src,
		})
	}
	return c.NoContent(http.StatusOK)

}

func (h *Handler) Action(c echo.Context) error {
	secret := config.Opts.SlackSecretKey
	version := "v0"
	timestamp := c.Request().Header.Get("X-Slack-Request-Timestamp")
	slackSignature := c.Request().Header.Get("X-Slack-Signature")
	body, err := ioutil.ReadAll(c.Request().Body)

	if err != nil {
		log.Error("Error parsing body", map[string]interface{}{"error": err.Error()})
		return c.NoContent(http.StatusBadRequest)
	}

	content := fmt.Sprintf("%s:%s:%s", version, timestamp, string(body))
	if !crypto.ValidateHMAC(content, slackSignature, version, secret) {
		log.Error("Error validating signature", nil)

		return c.NoContent(http.StatusBadRequest)
	}
	values, err := url.ParseQuery(string(body))
	if err != nil {
		log.Error("Error on parsing form values", map[string]interface{}{"error": err.Error()})
	}
	payload := values.Get("payload")
	actions.ProcessAction(payload, h.Session)
	return c.NoContent(http.StatusOK)

}

func isX9Action(action string) bool {
	listActions := []string{"opened", "edited", "reopened"}
	for _, v := range listActions {
		if action == v {
			return true
		}
	}
	return false
}


<|start_filename|>cmd/worker/actions/actions_test.go<|end_filename|>
package actions

import (
	"testing"

	"github.com/stretchr/testify/assert"
)

func TestGetRepoInfo(t *testing.T) {
	repository := "https://github.com/ResultadosDigitais/x9.git"
	expectedOwner := "ResultadosDigitais"
	expectedRepo := "x9"
	owner, repo := getRepoInfo(repository)
	assert.Equal(t, expectedOwner, owner)
	assert.Equal(t, expectedRepo, repo)
}


<|start_filename|>cmd/worker/git/git.go<|end_filename|>
package git

import (
	"context"
	"os"
	"strconv"
	"time"

	"github.com/ResultadosDigitais/x9/config"
	"github.com/google/go-github/github"
	"golang.org/x/oauth2"
	"gopkg.in/src-d/go-git.v4"
	"gopkg.in/src-d/go-git.v4/plumbing"
	"gopkg.in/src-d/go-git.v4/plumbing/transport/http"
)

type GithubSession struct {
	AccountName  string
	AccountToken string
	Timeout      int
	Context      context.Context
	Client       *github.Client
}

//ParseAuthConfig get the github accounts from environment
func (gs *GithubSession) ParseAuthConfig() error {
	gs.AccountName = config.Opts.GithubAccountUser
	gs.AccountToken = config.Opts.GithubAccountToken
	gs.Context = context.Background()

	return nil
}

func (gs *GithubSession) InitClient() error {
	if err := gs.ParseAuthConfig(); err != nil {
		return err
	}

	staticTokenSource := oauth2.StaticTokenSource(&oauth2.Token{AccessToken: gs.AccountToken})
	client := oauth2.NewClient(gs.Context, staticTokenSource)

	githubClient := github.NewClient(client)
	githubClient.UserAgent = "x9 v1.0"
	_, _, err := githubClient.Users.Get(gs.Context, "")
	if err != nil {
		return err
	}
	gs.Client = githubClient

	gs.Timeout = 60
	if timeout, ok := os.LookupEnv("CLONE_TIMEOUT"); ok {
		if intTimeout, err := strconv.Atoi(timeout); err == nil {
			gs.Timeout = intTimeout
		}
	}
	return nil
}

func (gs *GithubSession) CloneRepository(url, branch, dir string) (*git.Repository, error) {
	localCtx, cancel := context.WithTimeout(gs.Context, time.Duration(gs.Timeout)*time.Second)
	defer cancel()
	auth := &http.BasicAuth{Username: gs.AccountName, Password: <PASSWORD>}

	repository, err := git.PlainCloneContext(localCtx, dir, false, &git.CloneOptions{
		Depth:             1,
		RecurseSubmodules: git.NoRecurseSubmodules,
		URL:               url,
		ReferenceName:     plumbing.ReferenceName(branch),
		SingleBranch:      true,
		Tags:              git.NoTags,
		Auth:              auth,
	})

	if err != nil {
		return nil, err
	}

	return repository, nil
}

func (gs *GithubSession) GetRepository(id int64) (*github.Repository, error) {

	client := gs.Client
	repo, _, err := client.Repositories.GetByID(gs.Context, id)

	if err != nil {
		return nil, err
	}

	return repo, nil
}

func (gs *GithubSession) OpenIssue(owner, repo, title, message string, labels []string) (string, error) {
	client := gs.Client
	issue := github.IssueRequest{
		Title:  &title,
		Body:   &message,
		Labels: &labels,
	}
	i, _, err := client.Issues.Create(gs.Context, owner, repo, &issue)
	if i == nil {
		return "", err
	}
	return *i.HTMLURL, err
}
