Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 35 additions & 17 deletions pkg/connector/connector.go
Original file line number Diff line number Diff line change
Expand Up @@ -93,12 +93,13 @@ func (gh *GitHub) ResourceSyncers(ctx context.Context) []connectorbuilder.Resour
resourceSyncers := []connectorbuilder.ResourceSyncer{
orgBuilder(gh.client, gh.appClient, gh.orgCache, gh.orgs, gh.syncSecrets),
teamBuilder(gh.client, gh.orgCache),
userBuilder(gh.client, gh.hasSAMLEnabled, gh.graphqlClient, gh.orgCache),
userBuilder(gh.client, gh.hasSAMLEnabled, gh.graphqlClient, gh.orgCache, gh.orgs),
repositoryBuilder(gh.client, gh.orgCache),
orgRoleBuilder(gh.client, gh.orgCache),
invitationBuilder(invitationBuilderParams{
client: gh.client,
orgCache: gh.orgCache,
orgs: gh.orgs,
}),
}

Expand Down Expand Up @@ -145,28 +146,16 @@ func (gh *GitHub) Validate(ctx context.Context) (annotations.Annotations, error)
return gh.validateAppCredentials(ctx)
}

page := 0
orgLogins := gh.orgs
filterOrgs := true

if len(orgLogins) == 0 {
filterOrgs = false
for {
orgs, resp, err := gh.client.Organizations.List(ctx, "", &github.ListOptions{Page: page})
if err != nil {
return nil, fmt.Errorf("github-connector: failed to retrieve org: %w", err)
}
if resp.StatusCode == http.StatusUnauthorized {
return nil, status.Error(codes.Unauthenticated, "github token is not authorized")
}
for _, o := range orgs {
orgLogins = append(orgLogins, o.GetLogin())
}

if resp.NextPage == 0 {
break
}
page = resp.NextPage
var err error
orgLogins, err = getOrgs(ctx, gh.client, orgLogins)
if err != nil {
return nil, err
}
}

Expand Down Expand Up @@ -424,3 +413,32 @@ func (r *appTokenRefresher) Token() (*oauth2.Token, error) {
Expiry: token.GetExpiresAt().Time,
}, nil
}

func getOrgs(ctx context.Context, client *github.Client, orgs []string) ([]string, error) {
if len(orgs) != 0 {
return orgs, nil
}

var (
page = 0
orgLogins []string
)
for {
orgs, resp, err := client.Organizations.List(ctx, "", &github.ListOptions{Page: page})
if err != nil {
return nil, fmt.Errorf("github-connector: failed to retrieve org: %w", err)
}
if resp.StatusCode == http.StatusUnauthorized {
return nil, status.Error(codes.Unauthenticated, "github token is not authorized")
}
for _, o := range orgs {
orgLogins = append(orgLogins, o.GetLogin())
}

if resp.NextPage == 0 {
break
}
page = resp.NextPage
}
return orgLogins, nil
}
45 changes: 45 additions & 0 deletions pkg/connector/invitation.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package connector
import (
"context"
"fmt"
"strconv"

v2 "github.com/conductorone/baton-sdk/pb/c1/connector/v2"
"github.com/conductorone/baton-sdk/pkg/annotations"
Expand Down Expand Up @@ -41,6 +42,7 @@ func invitationToUserResource(invitation *github.Invitation) (*v2.Resource, erro
type invitationResourceType struct {
client *github.Client
orgCache *orgNameCache
orgs []string
}

func (i *invitationResourceType) ResourceType(_ context.Context) *v2.ResourceType {
Expand Down Expand Up @@ -153,6 +155,47 @@ func (i *invitationResourceType) CreateAccount(
}, nil, nil, nil
}

func (i *invitationResourceType) Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) {
Copy link
Contributor Author

@Bencheng21 Bencheng21 Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before deletion
image

After deletion. ./baton-github --delete-resource 65663067 --delete-resource-type invitation
image

if resourceId.ResourceType != resourceTypeInvitation.Id {
return nil, fmt.Errorf("baton-github: non-invitation resource passed to invitation delete")
}

orgs, err := getOrgs(ctx, i.client, i.orgs)
if err != nil {
return nil, err
}

invitationID, err := strconv.ParseInt(resourceId.GetResource(), 10, 64)
if err != nil {
return nil, fmt.Errorf("baton-github: invalid invitation id")
}

var (
isRemoved = false
resp *github.Response
)

for _, org := range orgs {
resp, err = i.client.Organizations.CancelInvite(ctx, org, invitationID)
if err == nil {
isRemoved = true
}
}

if !isRemoved {
return nil, fmt.Errorf("baton-github: failed to cancel invite")
}

restApiRateLimit, err := extractRateLimitData(resp)
if err != nil {
return nil, err
}

var annotations annotations.Annotations
annotations.WithRateLimiting(restApiRateLimit)
return annotations, nil
}

type createUserParams struct {
org string
email *string
Expand All @@ -179,11 +222,13 @@ func getCreateUserParams(accountInfo *v2.AccountInfo) (*createUserParams, error)
type invitationBuilderParams struct {
client *github.Client
orgCache *orgNameCache
orgs []string
}

func invitationBuilder(p invitationBuilderParams) *invitationResourceType {
return &invitationResourceType{
client: p.client,
orgCache: p.orgCache,
orgs: p.orgs,
}
}
49 changes: 48 additions & 1 deletion pkg/connector/user.go
Original file line number Diff line number Diff line change
Expand Up @@ -92,6 +92,7 @@ type userResourceType struct {
graphqlClient *githubv4.Client
hasSAMLEnabled *bool
orgCache *orgNameCache
orgs []string
}

func (o *userResourceType) ResourceType(_ context.Context) *v2.ResourceType {
Expand Down Expand Up @@ -224,13 +225,59 @@ func (o *userResourceType) Grants(_ context.Context, _ *v2.Resource, _ *paginati
return nil, "", nil, nil
}

func userBuilder(client *github.Client, hasSAMLEnabled *bool, graphqlClient *githubv4.Client, orgCache *orgNameCache) *userResourceType {
func (o *userResourceType) Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) {
Copy link
Contributor Author

@Bencheng21 Bencheng21 Jun 20, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Before change.
image

After changes ./baton-github --delete-resource 203686556 --delete-resource-type user
image

if resourceId.ResourceType != resourceTypeUser.Id {
return nil, fmt.Errorf("baton-github: non-user resource passed to user delete")
}

orgs, err := getOrgs(ctx, o.client, o.orgs)
if err != nil {
return nil, err
}

userID, err := strconv.ParseInt(resourceId.GetResource(), 10, 64)
if err != nil {
return nil, fmt.Errorf("baton-github: invalid invitation id")
}

u, _, err := o.client.Users.GetByID(ctx, userID)
if err != nil {
return nil, fmt.Errorf("baton-github: invalid userID")
}

var (
isRemoved = false
resp *github.Response
)
for _, org := range orgs {
resp, err = o.client.Organizations.RemoveOrgMembership(ctx, u.GetLogin(), org)
if err == nil {
isRemoved = true
}
}

if !isRemoved {
return nil, fmt.Errorf("baton-github: failed to cancel user")
}

restApiRateLimit, err := extractRateLimitData(resp)
if err != nil {
return nil, err
}

var annotations annotations.Annotations
annotations.WithRateLimiting(restApiRateLimit)
return annotations, nil
}

func userBuilder(client *github.Client, hasSAMLEnabled *bool, graphqlClient *githubv4.Client, orgCache *orgNameCache, orgs []string) *userResourceType {
return &userResourceType{
resourceType: resourceTypeUser,
client: client,
graphqlClient: graphqlClient,
hasSAMLEnabled: hasSAMLEnabled,
orgCache: orgCache,
orgs: orgs,
}
}

Expand Down
1 change: 1 addition & 0 deletions pkg/connector/user_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func TestUsersList(t *testing.T) {
testCase.hasSamlEnabled,
graphQLClient,
cache,
[]string{organization.DisplayName},
)

users, nextToken, annotations, err := client.List(
Expand Down
Loading