Path: blob/dev/pkg/external/customtemplates/github.go
2070 views
package customtemplates12import (3"context"4httpclient "net/http"5"path/filepath"6"strings"78"github.com/go-git/go-git/v5"9"github.com/go-git/go-git/v5/plumbing/transport/http"10"github.com/google/go-github/github"11"github.com/pkg/errors"12"github.com/projectdiscovery/gologger"13"github.com/projectdiscovery/nuclei/v3/pkg/catalog/config"14"github.com/projectdiscovery/nuclei/v3/pkg/types"15fileutil "github.com/projectdiscovery/utils/file"16folderutil "github.com/projectdiscovery/utils/folder"17"golang.org/x/oauth2"18)1920var _ Provider = &customTemplateGitHubRepo{}2122type customTemplateGitHubRepo struct {23owner string24reponame string25gitCloneURL string26githubToken string27}2829// This function download the custom github template repository30func (customTemplate *customTemplateGitHubRepo) Download(ctx context.Context) {31clonePath := customTemplate.getLocalRepoClonePath(config.DefaultConfig.CustomGitHubTemplatesDirectory)3233if !fileutil.FolderExists(clonePath) {34err := customTemplate.cloneRepo(clonePath, customTemplate.githubToken)35if err != nil {36gologger.Error().Msgf("%s", err)37} else {38gologger.Info().Msgf("Repo %s/%s cloned successfully at %s", customTemplate.owner, customTemplate.reponame, clonePath)39}40return41}42}4344func (customTemplate *customTemplateGitHubRepo) Update(ctx context.Context) {45downloadPath := config.DefaultConfig.CustomGitHubTemplatesDirectory46clonePath := customTemplate.getLocalRepoClonePath(downloadPath)4748// If folder does not exits then clone/download the repo49if !fileutil.FolderExists(clonePath) {50customTemplate.Download(ctx)51return52}53err := customTemplate.pullChanges(clonePath, customTemplate.githubToken)54if err != nil {55gologger.Error().Msgf("%s", err)56} else {57gologger.Info().Msgf("Repo %s/%s successfully pulled the changes.\n", customTemplate.owner, customTemplate.reponame)58}59}6061// NewGitHubProviders returns new instance of GitHub providers for downloading custom templates62func NewGitHubProviders(options *types.Options) ([]*customTemplateGitHubRepo, error) {63providers := []*customTemplateGitHubRepo{}64gitHubClient := getGHClientIncognito()6566if options.GitHubTemplateDisableDownload {67return providers, nil68}6970for _, repoName := range options.GitHubTemplateRepo {71owner, repo, err := getOwnerAndRepo(repoName)72if err != nil {73gologger.Error().Msgf("%s", err)74continue75}76githubRepo, err := getGitHubRepo(gitHubClient, owner, repo, options.GitHubToken)77if err != nil {78gologger.Error().Msgf("%s", err)79continue80}81customTemplateRepo := &customTemplateGitHubRepo{82owner: owner,83reponame: repo,84gitCloneURL: githubRepo.GetCloneURL(),85githubToken: options.GitHubToken,86}87providers = append(providers, customTemplateRepo)8889customTemplateRepo.restructureRepoDir()90}91return providers, nil92}9394func (customTemplateRepo *customTemplateGitHubRepo) restructureRepoDir() {95customGitHubTemplatesDirectory := config.DefaultConfig.CustomGitHubTemplatesDirectory96oldRepoClonePath := filepath.Join(customGitHubTemplatesDirectory, customTemplateRepo.reponame+"-"+customTemplateRepo.owner)97newRepoClonePath := customTemplateRepo.getLocalRepoClonePath(customGitHubTemplatesDirectory)9899if fileutil.FolderExists(oldRepoClonePath) && !fileutil.FolderExists(newRepoClonePath) {100_ = folderutil.SyncDirectory(oldRepoClonePath, newRepoClonePath)101}102}103104// getOwnerAndRepo returns the owner, repo, err from the given string105// e.g., it takes input projectdiscovery/nuclei-templates and106// returns owner => projectdiscovery, repo => nuclei-templates107func getOwnerAndRepo(reponame string) (owner string, repo string, err error) {108s := strings.Split(reponame, "/")109if len(s) != 2 {110err = errors.Errorf("wrong Repo name: %s", reponame)111return112}113owner = s[0]114repo = s[1]115return116}117118// returns *github.Repository if passed github repo name119func getGitHubRepo(gitHubClient *github.Client, repoOwner, repoName, githubToken string) (*github.Repository, error) {120var retried bool121getRepo:122repo, _, err := gitHubClient.Repositories.Get(context.Background(), repoOwner, repoName)123if err != nil {124// retry with authentication125if gitHubClient = getGHClientWithToken(githubToken); gitHubClient != nil && !retried {126retried = true127goto getRepo128}129return nil, err130}131if repo == nil {132return nil, errors.Errorf("problem getting repository: %s/%s", repoOwner, repoName)133}134return repo, nil135}136137// download the git repo to a given path138func (ctr *customTemplateGitHubRepo) cloneRepo(clonePath, githubToken string) error {139cloneOpts := &git.CloneOptions{140URL: ctr.gitCloneURL,141Auth: getAuth(ctr.owner, githubToken),142SingleBranch: true,143Depth: 1,144}145146err := cloneOpts.Validate()147if err != nil {148return err149}150151r, err := git.PlainClone(clonePath, false, cloneOpts)152if err != nil {153return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error())154}155156// Add the user as well in the config. By default, user is not set157config, _ := r.Storer.Config()158config.User.Name = ctr.owner159160return r.SetConfig(config)161}162163// performs the git pull on given repo164func (ctr *customTemplateGitHubRepo) pullChanges(repoPath, githubToken string) error {165pullOpts := &git.PullOptions{166RemoteName: "origin",167Auth: getAuth(ctr.owner, githubToken),168SingleBranch: true,169Depth: 1,170}171172err := pullOpts.Validate()173if err != nil {174return err175}176177r, err := git.PlainOpen(repoPath)178if err != nil {179return err180}181182w, err := r.Worktree()183if err != nil {184return err185}186187err = w.Pull(pullOpts)188if err != nil {189return errors.Errorf("%s/%s: %s", ctr.owner, ctr.reponame, err.Error())190}191192return nil193}194195// All Custom github repos are cloned in the format of 'owner/reponame' for uniqueness196func (ctr *customTemplateGitHubRepo) getLocalRepoClonePath(downloadPath string) string {197return filepath.Join(downloadPath, ctr.owner, ctr.reponame)198}199200// returns the auth object with username and github token as password201func getAuth(username, password string) *http.BasicAuth {202if username != "" && password != "" {203return &http.BasicAuth{Username: username, Password: password}204}205return nil206}207208func getGHClientWithToken(token string) *github.Client {209if token != "" {210ctx := context.Background()211ts := oauth2.StaticTokenSource(212&oauth2.Token{AccessToken: token},213)214oauthClient := oauth2.NewClient(ctx, ts)215return github.NewClient(oauthClient)216217}218return nil219}220221func getGHClientIncognito() *github.Client {222var tc *httpclient.Client223return github.NewClient(tc)224}225226227