diff --git a/pkg/connector/connector.go b/pkg/connector/connector.go index a52a7f49..79bd9c98 100644 --- a/pkg/connector/connector.go +++ b/pkg/connector/connector.go @@ -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, }), } @@ -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 } } @@ -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 +} diff --git a/pkg/connector/invitation.go b/pkg/connector/invitation.go index 7640b0d7..897a2c0a 100644 --- a/pkg/connector/invitation.go +++ b/pkg/connector/invitation.go @@ -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" @@ -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 { @@ -153,6 +155,47 @@ func (i *invitationResourceType) CreateAccount( }, nil, nil, nil } +func (i *invitationResourceType) Delete(ctx context.Context, resourceId *v2.ResourceId) (annotations.Annotations, error) { + 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 @@ -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, } } diff --git a/pkg/connector/user.go b/pkg/connector/user.go index eed15d18..cf139eeb 100644 --- a/pkg/connector/user.go +++ b/pkg/connector/user.go @@ -92,6 +92,7 @@ type userResourceType struct { graphqlClient *githubv4.Client hasSAMLEnabled *bool orgCache *orgNameCache + orgs []string } func (o *userResourceType) ResourceType(_ context.Context) *v2.ResourceType { @@ -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) { + 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, } } diff --git a/pkg/connector/user_test.go b/pkg/connector/user_test.go index a44b6365..0eeecc7d 100644 --- a/pkg/connector/user_test.go +++ b/pkg/connector/user_test.go @@ -49,6 +49,7 @@ func TestUsersList(t *testing.T) { testCase.hasSamlEnabled, graphQLClient, cache, + []string{organization.DisplayName}, ) users, nextToken, annotations, err := client.List(