Files
trufflehog/pkg/sources/github/connector.go
Richard GomezandKashif Khan bb899f20dc [GitHub] Add a GraphQL client to the connector (#3837)
* feat(github): add graphql client

* Update connector.go

* Update connector.go

* Update connector_basicauth.go

* Update connector_token.go

* Update connector_unauthenticated.go

* Update connector_app.go

---------

Co-authored-by: Kashif Khan <70996046+kashifkhan0771@users.noreply.github.com>
2025-08-26 11:25:38 +05:00

93 lines
3.5 KiB
Go

package github
import (
"fmt"
"net/http"
"net/url"
"strings"
gogit "github.com/go-git/go-git/v5"
"github.com/google/go-github/v67/github"
"github.com/shurcooL/githubv4"
"github.com/trufflesecurity/trufflehog/v3/pkg/context"
"github.com/trufflesecurity/trufflehog/v3/pkg/log"
"github.com/trufflesecurity/trufflehog/v3/pkg/pb/sourcespb"
)
const (
cloudV3Endpoint = "https://api.github.com"
cloudGraphqlEndpoint = "https://api.github.com/graphql" // https://docs.github.com/en/graphql/guides/forming-calls-with-graphql#the-graphql-endpoint
)
// Connector abstracts over the authenticated ways to interact with GitHub: cloning and API operations.
type Connector interface {
// APIClient returns a configured GitHub client that can be used for GitHub API operations.
APIClient() *github.Client
// GraphQLClient returns a client that can be used for GraphQL operations.
GraphQLClient() *githubv4.Client
// Clone clones a repository using the configured authentication information.
Clone(ctx context.Context, repoURL string, args ...string) (string, *gogit.Repository, error)
}
func newConnector(ctx context.Context, source *Source) (Connector, error) {
apiEndpoint := source.conn.Endpoint
if apiEndpoint == "" || endsWithGithub.MatchString(apiEndpoint) {
apiEndpoint = cloudV3Endpoint
}
switch cred := source.conn.GetCredential().(type) {
case *sourcespb.GitHub_GithubApp:
log.RedactGlobally(cred.GithubApp.GetPrivateKey())
return NewAppConnector(ctx, apiEndpoint, cred.GithubApp)
case *sourcespb.GitHub_BasicAuth:
log.RedactGlobally(cred.BasicAuth.GetPassword())
return NewBasicAuthConnector(ctx, apiEndpoint, source.conn.GetClonePath(), cred.BasicAuth)
case *sourcespb.GitHub_Token:
log.RedactGlobally(cred.Token)
return NewTokenConnector(ctx, apiEndpoint, cred.Token, source.conn.GetClonePath(), source.useAuthInUrl, func(c context.Context, err error) bool {
return source.handleRateLimit(c, err)
})
case *sourcespb.GitHub_Unauthenticated:
return NewUnauthenticatedConnector(ctx, apiEndpoint, source.conn.GetClonePath())
default:
return nil, fmt.Errorf("unknown connection type %T", source.conn.GetCredential())
}
}
func createAPIClient(ctx context.Context, httpClient *http.Client, apiEndpoint string) (*github.Client, error) {
ctx.Logger().V(2).Info("Creating API client", "url", apiEndpoint)
// If we're using public GitHub, make a regular client.
// Otherwise, make an enterprise client.
if strings.EqualFold(apiEndpoint, cloudV3Endpoint) {
return github.NewClient(httpClient), nil
}
return github.NewClient(httpClient).WithEnterpriseURLs(apiEndpoint, apiEndpoint)
}
func createGraphqlClient(ctx context.Context, client *http.Client, apiEndpoint string) (*githubv4.Client, error) {
var graphqlEndpoint string
if apiEndpoint == cloudV3Endpoint {
graphqlEndpoint = cloudGraphqlEndpoint
} else {
// Use the root endpoint for the host.
// https://docs.github.com/en/enterprise-server@3.11/graphql/guides/introduction-to-graphql
parsedURL, err := url.Parse(apiEndpoint)
if err != nil {
return nil, fmt.Errorf("error parsing URL: %w", err)
}
// GitHub Enterprise uses `/api/v3` for the base. (https://github.com/google/go-github/issues/958)
// Swap it, and anything before `/api`, with GraphQL.
before, _ := strings.CutSuffix(parsedURL.Path, "/api/v3")
parsedURL.Path = before + "/api/graphql"
graphqlEndpoint = parsedURL.String()
}
ctx.Logger().V(2).Info("Creating GraphQL client", "url", graphqlEndpoint)
return githubv4.NewEnterpriseClient(graphqlEndpoint, client), nil
}