Skip to content

Commit

Permalink
enhance: constants, helper funcs, reorganize client code, apply traci…
Browse files Browse the repository at this point in the history
…ng to app transports
  • Loading branch information
plyr4 committed Oct 29, 2024
1 parent 836d5cf commit 858da17
Show file tree
Hide file tree
Showing 15 changed files with 193 additions and 180 deletions.
6 changes: 3 additions & 3 deletions compiler/registry/github/github.go
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ func New(ctx context.Context, address, token string) (*client, error) {

if len(token) > 0 {
// create GitHub OAuth client with user's token
gitClient = c.newClientToken(ctx, token)
gitClient = c.newOAuthTokenClient(ctx, token)
}

// overwrite the github client
Expand All @@ -59,8 +59,8 @@ func New(ctx context.Context, address, token string) (*client, error) {
return c, nil
}

// newClientToken is a helper function to return the GitHub oauth2 client.
func (c *client) newClientToken(ctx context.Context, token string) *github.Client {
// newOAuthTokenClient is a helper function to return the GitHub oauth2 client.
func (c *client) newOAuthTokenClient(ctx context.Context, token string) *github.Client {
// create the token object for the client
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
Expand Down
2 changes: 1 addition & 1 deletion compiler/registry/github/template.go
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ func (c *client) Template(ctx context.Context, u *api.User, s *registry.Source)
cli := c.Github
if u != nil {
// create GitHub OAuth client with user's token
cli = c.newClientToken(ctx, u.GetToken())
cli = c.newOAuthTokenClient(ctx, u.GetToken())
}

// create the options to pass
Expand Down
10 changes: 5 additions & 5 deletions scm/github/access.go
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ func (c *client) OrgAccess(ctx context.Context, u *api.User, org string) (string
}

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, *u.Token)
client := c.newOAuthTokenClient(ctx, *u.Token)

// send API call to capture org access level for user
membership, _, err := client.Organizations.GetOrgMembership(ctx, *u.Name, org)
Expand Down Expand Up @@ -67,7 +67,7 @@ func (c *client) RepoAccess(ctx context.Context, name, token, org, repo string)
}

// create github oauth client with the given token
client := c.newClientToken(ctx, token)
client := c.newOAuthTokenClient(ctx, token)

// send API call to capture repo access level for user
perm, _, err := client.Repositories.GetPermissionLevel(ctx, org, repo, name)
Expand Down Expand Up @@ -98,7 +98,7 @@ func (c *client) TeamAccess(ctx context.Context, u *api.User, org, team string)
}

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, u.GetToken())
client := c.newOAuthTokenClient(ctx, u.GetToken())
teams := []*github.Team{}

// set the max per page for the options to capture the list of repos
Expand Down Expand Up @@ -148,7 +148,7 @@ func (c *client) ListUsersTeamsForOrg(ctx context.Context, u *api.User, org stri
}).Tracef("capturing %s team membership for org %s", u.GetName(), org)

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, u.GetToken())
client := c.newOAuthTokenClient(ctx, u.GetToken())
teams := []*github.Team{}

// set the max per page for the options to capture the list of repos
Expand Down Expand Up @@ -193,7 +193,7 @@ func (c *client) RepoContributor(ctx context.Context, owner *api.User, sender, o
}).Tracef("capturing %s contributor status for repo %s/%s", sender, org, repo)

// create GitHub OAuth client with repo owner's token
client := c.newClientToken(ctx, owner.GetToken())
client := c.newOAuthTokenClient(ctx, owner.GetToken())

// set the max per page for the options to capture the list of repos
opts := github.ListContributorsOptions{
Expand Down
142 changes: 39 additions & 103 deletions scm/github/app_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,118 +4,54 @@ package github

import (
"context"
"crypto/x509"
"encoding/base64"
"encoding/pem"
"errors"
"fmt"
"net/http"
"net/http/httptrace"
"net/url"
"strings"

"github.com/google/go-github/v65/github"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
"golang.org/x/oauth2"

api "github.com/go-vela/server/api/types"
"github.com/go-vela/server/constants"
)

// NewGitHubAppTransport creates a new GitHub App transport for authenticating as the GitHub App.
func NewGitHubAppTransport(appID int64, privateKey, baseURL string) (*AppsTransport, error) {
decodedPEM, err := base64.StdEncoding.DecodeString(privateKey)
if err != nil {
return nil, fmt.Errorf("error decoding base64: %w", err)
}

block, _ := pem.Decode(decodedPEM)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the key")
}

_privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
}

transport := NewAppsTransportFromPrivateKey(http.DefaultTransport, appID, _privateKey)
transport.BaseURL = baseURL

return transport, nil
}

// ValidateGitHubApp ensures the GitHub App configuration is valid.
func (c *client) ValidateGitHubApp(ctx context.Context) error {
client, err := c.newGithubAppClient()
if err != nil {
return fmt.Errorf("error creating github app client: %w", err)
}

app, _, err := client.Apps.Get(ctx, "")
if err != nil {
return fmt.Errorf("error getting github app: %w", err)
}

perms := app.GetPermissions()

type perm struct {
resource string
requiredPermission string
actualPermission string
}

// GitHub App installation requires the following permissions
// - contents:read
// - checks:write
requiredPermissions := []perm{
{
resource: constants.AppInstallResourceContents,
requiredPermission: constants.AppInstallPermissionRead,
actualPermission: perms.GetContents(),
},
{
resource: constants.AppInstallResourceChecks,
requiredPermission: constants.AppInstallPermissionWrite,
actualPermission: perms.GetChecks(),
},
}

for _, p := range requiredPermissions {
err := hasPermission(p.resource, p.requiredPermission, p.actualPermission)
if err != nil {
return err
}
}

return nil
}

// hasPermission takes a resource:perm pair and checks if the actual permission matches the expected permission or is supersceded by a higher permission.
func hasPermission(resource, requiredPerm, actualPerm string) error {
if len(actualPerm) == 0 {
return fmt.Errorf("github app missing permission %s:%s", resource, requiredPerm)
}

permitted := false

switch requiredPerm {
case constants.AppInstallPermissionNone:
permitted = true
case constants.AppInstallPermissionRead:
if actualPerm == constants.AppInstallPermissionRead ||
actualPerm == constants.AppInstallPermissionWrite {
permitted = true
}
case constants.AppInstallPermissionWrite:
if actualPerm == constants.AppInstallPermissionWrite {
permitted = true
}
default:
return fmt.Errorf("invalid required permission type: %s", requiredPerm)
}

if !permitted {
return fmt.Errorf("github app requires permission %s:%s, found: %s", constants.AppInstallResourceContents, constants.AppInstallPermissionRead, actualPerm)
}

return nil
// newOAuthTokenClient returns the GitHub OAuth client.
func (c *client) newOAuthTokenClient(ctx context.Context, token string) *github.Client {
// create the token object for the client
ts := oauth2.StaticTokenSource(
&oauth2.Token{AccessToken: token},
)

// create the OAuth client
tc := oauth2.NewClient(ctx, ts)
// if c.SkipVerify {
// tc.Transport.(*oauth2.Transport).Base = &http.Transport{
// Proxy: http.ProxyFromEnvironment,
// TLSClientConfig: &tls.Config{
// InsecureSkipVerify: true,
// },
// }
// }

if c.Tracing.Config.EnableTracing {
tc.Transport = otelhttp.NewTransport(
tc.Transport,
otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
return otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans())
}),
)
}

// create the GitHub client from the OAuth client
github := github.NewClient(tc)

// ensure the proper URL is set in the GitHub client
github.BaseURL, _ = url.Parse(c.config.API)

return github
}

// newGithubAppClient returns the GitHub App client for authenticating as the GitHub App itself using the RoundTripper.
Expand Down
43 changes: 41 additions & 2 deletions scm/github/app_transport.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,24 @@ import (
"bytes"
"context"
"crypto/rsa"
"crypto/x509"
"encoding/base64"
"encoding/json"
"encoding/pem"
"errors"
"fmt"
"io"
"net/http"
"net/http/httptrace"
"strconv"
"strings"
"sync"
"time"

"github.com/golang-jwt/jwt/v5"
"github.com/google/go-github/v65/github"
"go.opentelemetry.io/contrib/instrumentation/net/http/httptrace/otelhttptrace"
"go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp"
)

const (
Expand All @@ -40,8 +46,41 @@ type AppsTransport struct {
appID int64 // appID is the GitHub App's ID
}

// NewAppsTransportFromPrivateKey returns an AppsTransport using a crypto/rsa.(*PrivateKey).
func NewAppsTransportFromPrivateKey(tr http.RoundTripper, appID int64, key *rsa.PrivateKey) *AppsTransport {
// newGitHubAppTransport creates a new GitHub App transport for authenticating as the GitHub App.
func (c *client) newGitHubAppTransport(appID int64, privateKey, baseURL string) (*AppsTransport, error) {
decodedPEM, err := base64.StdEncoding.DecodeString(privateKey)
if err != nil {
return nil, fmt.Errorf("error decoding base64: %w", err)
}

block, _ := pem.Decode(decodedPEM)
if block == nil {
return nil, fmt.Errorf("failed to parse PEM block containing the key")
}

_privateKey, err := x509.ParsePKCS1PrivateKey(block.Bytes)
if err != nil {
return nil, fmt.Errorf("failed to parse RSA private key: %w", err)
}

transport := c.newAppsTransportFromPrivateKey(http.DefaultTransport, appID, _privateKey)
transport.BaseURL = baseURL

// apply tracing to the transport
if c.Tracing.Config.EnableTracing {
transport.tr = otelhttp.NewTransport(
transport.tr,
otelhttp.WithClientTrace(func(ctx context.Context) *httptrace.ClientTrace {
return otelhttptrace.NewClientTrace(ctx, otelhttptrace.WithoutSubSpans())
}),
)
}

return transport, nil
}

// newAppsTransportFromPrivateKey returns an AppsTransport using a crypto/rsa.(*PrivateKey).
func (c *client) newAppsTransportFromPrivateKey(tr http.RoundTripper, appID int64, key *rsa.PrivateKey) *AppsTransport {
return &AppsTransport{
BaseURL: apiBaseURL,
Client: &http.Client{Transport: tr},
Expand Down
2 changes: 1 addition & 1 deletion scm/github/authentication.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (c *client) Authorize(ctx context.Context, token string) (string, error) {
c.Logger.Trace("authorizing user with token")

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, token)
client := c.newOAuthTokenClient(ctx, token)

// send API call to capture the current user making the call
u, _, err := client.Users.Get(ctx, "")
Expand Down
4 changes: 2 additions & 2 deletions scm/github/changeset.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func (c *client) Changeset(ctx context.Context, r *api.Repo, sha string) ([]stri
}).Tracef("capturing commit changeset for %s/commit/%s", r.GetFullName(), sha)

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, r.GetOwner().GetToken())
client := c.newOAuthTokenClient(ctx, r.GetOwner().GetToken())
s := []string{}

// set the max per page for the options to capture the commit
Expand Down Expand Up @@ -50,7 +50,7 @@ func (c *client) ChangesetPR(ctx context.Context, r *api.Repo, number int) ([]st
}).Tracef("capturing pull request changeset for %s/pull/%d", r.GetFullName(), number)

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, r.GetOwner().GetToken())
client := c.newOAuthTokenClient(ctx, r.GetOwner().GetToken())
s := []string{}
f := []*github.CommitFile{}

Expand Down
8 changes: 4 additions & 4 deletions scm/github/deployment.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ func (c *client) GetDeployment(ctx context.Context, u *api.User, r *api.Repo, id
}).Tracef("capturing deployment %d for repo %s", id, r.GetFullName())

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, *u.Token)
client := c.newOAuthTokenClient(ctx, *u.Token)

// send API call to capture the deployment
deployment, _, err := client.Repositories.GetDeployment(ctx, r.GetOrg(), r.GetName(), id)
Expand Down Expand Up @@ -63,7 +63,7 @@ func (c *client) GetDeploymentCount(ctx context.Context, u *api.User, r *api.Rep
}).Tracef("counting deployments for repo %s", r.GetFullName())

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, *u.Token)
client := c.newOAuthTokenClient(ctx, *u.Token)
// create variable to track the deployments
deployments := []*github.Deployment{}

Expand Down Expand Up @@ -105,7 +105,7 @@ func (c *client) GetDeploymentList(ctx context.Context, u *api.User, r *api.Repo
}).Tracef("listing deployments for repo %s", r.GetFullName())

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, *u.Token)
client := c.newOAuthTokenClient(ctx, *u.Token)

// set pagination options for listing deployments
opts := &github.DeploymentsListOptions{
Expand Down Expand Up @@ -164,7 +164,7 @@ func (c *client) CreateDeployment(ctx context.Context, u *api.User, r *api.Repo,
}).Tracef("creating deployment for repo %s", r.GetFullName())

// create GitHub OAuth client with user's token
client := c.newClientToken(ctx, *u.Token)
client := c.newOAuthTokenClient(ctx, *u.Token)

var payload interface{}
if d.Payload == nil {
Expand Down
Loading

0 comments on commit 858da17

Please sign in to comment.