diff --git a/.gitignore b/.gitignore index 3d439783c94..aea73858e55 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,5 @@ coverage.out # intellij files .idea/ vendor/ -.DS_Store \ No newline at end of file +.DS_Store +.vscode \ No newline at end of file diff --git a/github/github-accessors.go b/github/github-accessors.go index bd94ba944c2..9bdcab00673 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -7180,6 +7180,30 @@ func (l *ListRepositories) GetTotalCount() int { return *l.TotalCount } +// GetCount returns the Count field if it's non-nil, zero value otherwise. +func (l *ListSCIMProvisionedIdentitiesOptions) GetCount() int { + if l == nil || l.Count == nil { + return 0 + } + return *l.Count +} + +// GetFilter returns the Filter field if it's non-nil, zero value otherwise. +func (l *ListSCIMProvisionedIdentitiesOptions) GetFilter() string { + if l == nil || l.Filter == nil { + return "" + } + return *l.Filter +} + +// GetStartIndex returns the StartIndex field if it's non-nil, zero value otherwise. +func (l *ListSCIMProvisionedIdentitiesOptions) GetStartIndex() int { + if l == nil || l.StartIndex == nil { + return 0 + } + return *l.StartIndex +} + // GetEndColumn returns the EndColumn field if it's non-nil, zero value otherwise. func (l *Location) GetEndColumn() int { if l == nil || l.EndColumn == nil { @@ -14572,6 +14596,54 @@ func (r *RunnerLabels) GetType() string { return *r.Type } +// GetActive returns the Active field if it's non-nil, zero value otherwise. +func (s *SCIMUserAttributes) GetActive() bool { + if s == nil || s.Active == nil { + return false + } + return *s.Active +} + +// GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. +func (s *SCIMUserAttributes) GetDisplayName() string { + if s == nil || s.DisplayName == nil { + return "" + } + return *s.DisplayName +} + +// GetExternalID returns the ExternalID field if it's non-nil, zero value otherwise. +func (s *SCIMUserAttributes) GetExternalID() string { + if s == nil || s.ExternalID == nil { + return "" + } + return *s.ExternalID +} + +// GetPrimary returns the Primary field if it's non-nil, zero value otherwise. +func (s *SCIMUserEmail) GetPrimary() bool { + if s == nil || s.Primary == nil { + return false + } + return *s.Primary +} + +// GetType returns the Type field if it's non-nil, zero value otherwise. +func (s *SCIMUserEmail) GetType() string { + if s == nil || s.Type == nil { + return "" + } + return *s.Type +} + +// GetFormatted returns the Formatted field if it's non-nil, zero value otherwise. +func (s *SCIMUserName) GetFormatted() string { + if s == nil || s.Formatted == nil { + return "" + } + return *s.Formatted +} + // GetTotalCount returns the TotalCount field if it's non-nil, zero value otherwise. func (s *SelectedReposList) GetTotalCount() int { if s == nil || s.TotalCount == nil { @@ -16116,6 +16188,14 @@ func (t *TreeEntry) GetURL() string { return *t.URL } +// GetPath returns the Path field if it's non-nil, zero value otherwise. +func (u *UpdateAttributeForSCIMUserOperations) GetPath() string { + if u == nil || u.Path == nil { + return "" + } + return *u.Path +} + // GetCompletedAt returns the CompletedAt field if it's non-nil, zero value otherwise. func (u *UpdateCheckRunOptions) GetCompletedAt() Timestamp { if u == nil || u.CompletedAt == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 4264ce8b9ad..10029493502 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -8427,6 +8427,36 @@ func TestListRepositories_GetTotalCount(tt *testing.T) { l.GetTotalCount() } +func TestListSCIMProvisionedIdentitiesOptions_GetCount(tt *testing.T) { + var zeroValue int + l := &ListSCIMProvisionedIdentitiesOptions{Count: &zeroValue} + l.GetCount() + l = &ListSCIMProvisionedIdentitiesOptions{} + l.GetCount() + l = nil + l.GetCount() +} + +func TestListSCIMProvisionedIdentitiesOptions_GetFilter(tt *testing.T) { + var zeroValue string + l := &ListSCIMProvisionedIdentitiesOptions{Filter: &zeroValue} + l.GetFilter() + l = &ListSCIMProvisionedIdentitiesOptions{} + l.GetFilter() + l = nil + l.GetFilter() +} + +func TestListSCIMProvisionedIdentitiesOptions_GetStartIndex(tt *testing.T) { + var zeroValue int + l := &ListSCIMProvisionedIdentitiesOptions{StartIndex: &zeroValue} + l.GetStartIndex() + l = &ListSCIMProvisionedIdentitiesOptions{} + l.GetStartIndex() + l = nil + l.GetStartIndex() +} + func TestLocation_GetEndColumn(tt *testing.T) { var zeroValue int l := &Location{EndColumn: &zeroValue} @@ -17046,6 +17076,66 @@ func TestRunnerLabels_GetType(tt *testing.T) { r.GetType() } +func TestSCIMUserAttributes_GetActive(tt *testing.T) { + var zeroValue bool + s := &SCIMUserAttributes{Active: &zeroValue} + s.GetActive() + s = &SCIMUserAttributes{} + s.GetActive() + s = nil + s.GetActive() +} + +func TestSCIMUserAttributes_GetDisplayName(tt *testing.T) { + var zeroValue string + s := &SCIMUserAttributes{DisplayName: &zeroValue} + s.GetDisplayName() + s = &SCIMUserAttributes{} + s.GetDisplayName() + s = nil + s.GetDisplayName() +} + +func TestSCIMUserAttributes_GetExternalID(tt *testing.T) { + var zeroValue string + s := &SCIMUserAttributes{ExternalID: &zeroValue} + s.GetExternalID() + s = &SCIMUserAttributes{} + s.GetExternalID() + s = nil + s.GetExternalID() +} + +func TestSCIMUserEmail_GetPrimary(tt *testing.T) { + var zeroValue bool + s := &SCIMUserEmail{Primary: &zeroValue} + s.GetPrimary() + s = &SCIMUserEmail{} + s.GetPrimary() + s = nil + s.GetPrimary() +} + +func TestSCIMUserEmail_GetType(tt *testing.T) { + var zeroValue string + s := &SCIMUserEmail{Type: &zeroValue} + s.GetType() + s = &SCIMUserEmail{} + s.GetType() + s = nil + s.GetType() +} + +func TestSCIMUserName_GetFormatted(tt *testing.T) { + var zeroValue string + s := &SCIMUserName{Formatted: &zeroValue} + s.GetFormatted() + s = &SCIMUserName{} + s.GetFormatted() + s = nil + s.GetFormatted() +} + func TestSelectedReposList_GetTotalCount(tt *testing.T) { var zeroValue int s := &SelectedReposList{TotalCount: &zeroValue} @@ -18847,6 +18937,16 @@ func TestTreeEntry_GetURL(tt *testing.T) { t.GetURL() } +func TestUpdateAttributeForSCIMUserOperations_GetPath(tt *testing.T) { + var zeroValue string + u := &UpdateAttributeForSCIMUserOperations{Path: &zeroValue} + u.GetPath() + u = &UpdateAttributeForSCIMUserOperations{} + u.GetPath() + u = nil + u.GetPath() +} + func TestUpdateCheckRunOptions_GetCompletedAt(tt *testing.T) { var zeroValue Timestamp u := &UpdateCheckRunOptions{CompletedAt: &zeroValue} diff --git a/github/github.go b/github/github.go index 56b63d17a24..b83e4306c96 100644 --- a/github/github.go +++ b/github/github.go @@ -181,6 +181,7 @@ type Client struct { PullRequests *PullRequestsService Reactions *ReactionsService Repositories *RepositoriesService + SCIM *SCIMService Search *SearchService Teams *TeamsService Users *UsersService @@ -308,6 +309,7 @@ func NewClient(httpClient *http.Client) *Client { c.PullRequests = (*PullRequestsService)(&c.common) c.Reactions = (*ReactionsService)(&c.common) c.Repositories = (*RepositoriesService)(&c.common) + c.SCIM = (*SCIMService)(&c.common) c.Search = (*SearchService)(&c.common) c.Teams = (*TeamsService)(&c.common) c.Users = (*UsersService)(&c.common) diff --git a/github/scim.go b/github/scim.go new file mode 100644 index 00000000000..7a12d85b883 --- /dev/null +++ b/github/scim.go @@ -0,0 +1,163 @@ +// Copyright 2021 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "encoding/json" + "fmt" +) + +// SCIMService provides access to SCIM related functions in the +// GitHub API. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim +type SCIMService service + +// SCIMUserAttributes represents supported SCIM User attributes. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#supported-scim-user-attributes +type SCIMUserAttributes struct { + UserName string `json:"userName"` // Configured by the admin. Could be an email, login, or username. (Required.) + Name SCIMUserName `json:"name"` // (Required.) + DisplayName *string `json:"displayName,omitempty"` // The name of the user, suitable for display to end-users. (Optional.) + Emails []*SCIMUserEmail `json:"emails"` // User emails. (Required.) + Schemas []string `json:"schemas,omitempty"` // (Optional.) + ExternalID *string `json:"externalId,omitempty"` // (Optional.) + Groups []string `json:"groups,omitempty"` // (Optional.) + Active *bool `json:"active,omitempty"` // (Optional.) +} + +// SCIMUserName represents SCIM user information. +type SCIMUserName struct { + GivenName string `json:"givenName"` // The first name of the user. (Required.) + FamilyName string `json:"familyName"` // The family name of the user. (Required.) + Formatted *string `json:"formatted,omitempty"` // (Optional.) +} + +//SCIMUserEmail represents SCIM user email. +type SCIMUserEmail struct { + Value string `json:"value"` // (Required.) + Primary *bool `json:"primary,omitempty"` // (Optional.) + Type *string `json:"type,omitempty"` // (Optional.) +} + +// ListSCIMProvisionedIdentitiesOptions represents options for ListSCIMProvisionedIdentities. +// +// Github API docs: https://docs.github.com/en/rest/reference/scim#list-scim-provisioned-identities--parameters +type ListSCIMProvisionedIdentitiesOptions struct { + StartIndex *int `json:"startIndex,omitempty"` // Used for pagination: the index of the first result to return. (Optional.) + Count *int `json:"count,omitempty"` // Used for pagination: the number of results to return. (Optional.) + // Filter results using the equals query parameter operator (eq). + // You can filter results that are equal to id, userName, emails, and external_id. + // For example, to search for an identity with the userName Octocat, you would use this query: ?filter=userName%20eq%20\"Octocat\". + // To filter results for the identity with the email octocat@github.com, you would use this query: ?filter=emails%20eq%20\"octocat@github.com\". + // (Optional.) + Filter *string `json:"filter,omitempty"` +} + +// ListSCIMProvisionedIdentities lists SCIM provisioned identities. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#list-scim-provisioned-identities +func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org string, opts *ListSCIMProvisionedIdentitiesOptions) (*Response, error) { + u := fmt.Sprintf("scim/v2/organizations/%v/Users", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, err + } + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// ProvisionAndInviteSCIMUser provisions organization membership for a user, and sends an activation email to the email address. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#provision-and-invite-a-scim-user +func (s *SCIMService) ProvisionAndInviteSCIMUser(ctx context.Context, org string, opts *SCIMUserAttributes) (*Response, error) { + u := fmt.Sprintf("scim/v2/organizations/%v/Users", org) + u, err := addOptions(u, opts) + if err != nil { + return nil, err + } + req, err := s.client.NewRequest("POST", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// GetSCIMProvisioningInfoForUser returns SCIM provisioning information for a user. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#get-scim-provisioning-information-for-a-user +func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, scimUserID string) (*Response, error) { + u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID) + req, err := s.client.NewRequest("GET", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// UpdateProvisionedOrgMembership updates a provisioned organization membership. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#update-a-provisioned-organization-membership +func (s *SCIMService) UpdateProvisionedOrgMembership(ctx context.Context, org, scimUserID string, opts *SCIMUserAttributes) (*Response, error) { + u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID) + u, err := addOptions(u, opts) + if err != nil { + return nil, err + } + req, err := s.client.NewRequest("PUT", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// UpdateAttributeForSCIMUserOptions represents options for UpdateAttributeForSCIMUser. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#update-an-attribute-for-a-scim-user--parameters +type UpdateAttributeForSCIMUserOptions struct { + Schemas []string `json:"schemas,omitempty"` // (Optional.) + Operations UpdateAttributeForSCIMUserOperations `json:"operations"` // Set of operations to be performed. (Required.) +} + +// UpdateAttributeForSCIMUserOperations represents operations for UpdateAttributeForSCIMUser. +type UpdateAttributeForSCIMUserOperations struct { + Op string `json:"op"` // (Required.) + Path *string `json:"path,omitempty"` // (Optional.) + Value json.RawMessage `json:"value,omitempty"` // (Optional.) +} + +// UpdateAttributeForSCIMUser updates an attribute for an SCIM user. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#update-an-attribute-for-a-scim-user +func (s *SCIMService) UpdateAttributeForSCIMUser(ctx context.Context, org, scimUserID string, opts *UpdateAttributeForSCIMUserOptions) (*Response, error) { + u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID) + u, err := addOptions(u, opts) + if err != nil { + return nil, err + } + req, err := s.client.NewRequest("PATCH", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} + +// DeleteSCIMUserFromOrg deletes SCIM user from an organization. +// +// GitHub API docs: https://docs.github.com/en/rest/reference/scim#delete-a-scim-user-from-an-organization +func (s *SCIMService) DeleteSCIMUserFromOrg(ctx context.Context, org, scimUserID string) (*Response, error) { + u := fmt.Sprintf("scim/v2/organizations/%v/Users/%v", org, scimUserID) + req, err := s.client.NewRequest("DELETE", u, nil) + if err != nil { + return nil, err + } + return s.client.Do(ctx, req, nil) +} diff --git a/github/scim_test.go b/github/scim_test.go new file mode 100644 index 00000000000..3338a28a26c --- /dev/null +++ b/github/scim_test.go @@ -0,0 +1,194 @@ +// Copyright 2021 The go-github AUTHORS. All rights reserved. +// +// Use of this source code is governed by a BSD-style +// license that can be found in the LICENSE file. + +package github + +import ( + "context" + "net/http" + "testing" +) + +func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/scim/v2/organizations/o/Users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusOK) + }) + + ctx := context.Background() + opts := &ListSCIMProvisionedIdentitiesOptions{} + _, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts) + if err != nil { + t.Errorf("SCIM.ListSCIMProvisionedIdentities returned error: %v", err) + } + + const methodName = "ListSCIMProvisionedIdentities" + testBadOptions(t, methodName, func() (err error) { + _, err = client.SCIM.ListSCIMProvisionedIdentities(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts) + }) +} + +func TestSCIMService_ProvisionAndInviteSCIMUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/scim/v2/organizations/o/Users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + w.WriteHeader(http.StatusOK) + }) + + ctx := context.Background() + opts := &SCIMUserAttributes{ + UserName: "userName", + Name: SCIMUserName{ + GivenName: "givenName", + FamilyName: "familyName", + }, + Emails: []*SCIMUserEmail{ + { + Value: "octocat@github.com", + }, + }, + } + _, err := client.SCIM.ProvisionAndInviteSCIMUser(ctx, "o", opts) + if err != nil { + t.Errorf("SCIM.ListSCIMProvisionedIdentities returned error: %v", err) + } + + const methodName = "ProvisionAndInviteSCIMUser" + testBadOptions(t, methodName, func() (err error) { + _, err = client.SCIM.ProvisionAndInviteSCIMUser(ctx, "\n", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.SCIM.ProvisionAndInviteSCIMUser(ctx, "o", opts) + }) +} + +func TestSCIMService_GetSCIMProvisioningInfoForUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "GET") + w.WriteHeader(http.StatusOK) + }) + + ctx := context.Background() + _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123") + if err != nil { + t.Errorf("SCIM.GetSCIMProvisioningInfoForUser returned error: %v", err) + } + + const methodName = "GetSCIMProvisioningInfoForUser" + testBadOptions(t, methodName, func() error { + _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "\n", "123") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123") + }) +} + +func TestSCIMService_UpdateProvisionedOrgMembership(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PUT") + w.WriteHeader(http.StatusOK) + }) + + ctx := context.Background() + opts := &SCIMUserAttributes{ + UserName: "userName", + Name: SCIMUserName{ + GivenName: "givenName", + FamilyName: "familyName", + }, + Emails: []*SCIMUserEmail{ + { + Value: "octocat@github.com", + }, + }, + } + _, err := client.SCIM.UpdateProvisionedOrgMembership(ctx, "o", "123", opts) + if err != nil { + t.Errorf("SCIM.UpdateProvisionedOrgMembership returned error: %v", err) + } + + const methodName = "UpdateProvisionedOrgMembership" + testBadOptions(t, methodName, func() error { + _, err := client.SCIM.UpdateProvisionedOrgMembership(ctx, "\n", "123", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.SCIM.UpdateProvisionedOrgMembership(ctx, "o", "123", opts) + }) +} + +func TestSCIMService_UpdateAttributeForSCIMUser(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "PATCH") + w.WriteHeader(http.StatusNoContent) + }) + + ctx := context.Background() + opts := &UpdateAttributeForSCIMUserOptions{} + _, err := client.SCIM.UpdateAttributeForSCIMUser(ctx, "o", "123", opts) + if err != nil { + t.Errorf("SCIM.UpdateAttributeForSCIMUser returned error: %v", err) + } + + const methodName = "UpdateAttributeForSCIMUser" + testBadOptions(t, methodName, func() error { + _, err := client.SCIM.UpdateAttributeForSCIMUser(ctx, "\n", "123", opts) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.SCIM.UpdateAttributeForSCIMUser(ctx, "o", "123", opts) + }) +} + +func TestSCIMService_DeleteSCIMUserFromOrg(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "DELETE") + w.WriteHeader(http.StatusNoContent) + }) + + ctx := context.Background() + _, err := client.SCIM.DeleteSCIMUserFromOrg(ctx, "o", "123") + if err != nil { + t.Errorf("SCIM.DeleteSCIMUserFromOrg returned error: %v", err) + } + + const methodName = "DeleteSCIMUserFromOrg" + testBadOptions(t, methodName, func() error { + _, err := client.SCIM.DeleteSCIMUserFromOrg(ctx, "\n", "") + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + return client.SCIM.DeleteSCIMUserFromOrg(ctx, "o", "123") + }) +}