From 7018cf3b9f196210f1b9c11847869e7272bba6fd Mon Sep 17 00:00:00 2001 From: tenyo Date: Tue, 20 Sep 2022 19:07:58 -0400 Subject: [PATCH 1/3] Return proper responses for SCIM provisioned identities --- github/github-accessors.go | 72 ++++++++++++++ github/github-accessors_test.go | 87 +++++++++++++++++ github/scim.go | 44 +++++++-- github/scim_test.go | 161 ++++++++++++++++++++++++++++++-- 4 files changed, 351 insertions(+), 13 deletions(-) diff --git a/github/github-accessors.go b/github/github-accessors.go index 398ed7a3b4..72c4b65fac 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -16582,6 +16582,62 @@ func (s *ScanningAnalysis) GetWarning() string { return *s.Warning } +// GetCreated returns the Created field if it's non-nil, zero value otherwise. +func (s *SCIMMeta) GetCreated() Timestamp { + if s == nil || s.Created == nil { + return Timestamp{} + } + return *s.Created +} + +// GetLastModified returns the LastModified field if it's non-nil, zero value otherwise. +func (s *SCIMMeta) GetLastModified() Timestamp { + if s == nil || s.LastModified == nil { + return Timestamp{} + } + return *s.LastModified +} + +// GetLocation returns the Location field if it's non-nil, zero value otherwise. +func (s *SCIMMeta) GetLocation() string { + if s == nil || s.Location == nil { + return "" + } + return *s.Location +} + +// GetResourceType returns the ResourceType field if it's non-nil, zero value otherwise. +func (s *SCIMMeta) GetResourceType() string { + if s == nil || s.ResourceType == nil { + return "" + } + return *s.ResourceType +} + +// GetItemsPerPage returns the ItemsPerPage field if it's non-nil, zero value otherwise. +func (s *SCIMProvisionedIdentities) GetItemsPerPage() int { + if s == nil || s.ItemsPerPage == nil { + return 0 + } + return *s.ItemsPerPage +} + +// GetStartIndex returns the StartIndex field if it's non-nil, zero value otherwise. +func (s *SCIMProvisionedIdentities) GetStartIndex() int { + if s == nil || s.StartIndex == nil { + return 0 + } + return *s.StartIndex +} + +// GetTotalResults returns the TotalResults field if it's non-nil, zero value otherwise. +func (s *SCIMProvisionedIdentities) GetTotalResults() int { + if s == nil || s.TotalResults == nil { + return 0 + } + return *s.TotalResults +} + // GetActive returns the Active field if it's non-nil, zero value otherwise. func (s *SCIMUserAttributes) GetActive() bool { if s == nil || s.Active == nil { @@ -16606,6 +16662,22 @@ func (s *SCIMUserAttributes) GetExternalID() string { return *s.ExternalID } +// GetID returns the ID field if it's non-nil, zero value otherwise. +func (s *SCIMUserAttributes) GetID() string { + if s == nil || s.ID == nil { + return "" + } + return *s.ID +} + +// GetMeta returns the Meta field. +func (s *SCIMUserAttributes) GetMeta() *SCIMMeta { + if s == nil { + return nil + } + return s.Meta +} + // GetPrimary returns the Primary field if it's non-nil, zero value otherwise. func (s *SCIMUserEmail) GetPrimary() bool { if s == nil || s.Primary == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index e3d565b8cc..6128544319 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -19351,6 +19351,76 @@ func TestScanningAnalysis_GetWarning(tt *testing.T) { s.GetWarning() } +func TestSCIMMeta_GetCreated(tt *testing.T) { + var zeroValue Timestamp + s := &SCIMMeta{Created: &zeroValue} + s.GetCreated() + s = &SCIMMeta{} + s.GetCreated() + s = nil + s.GetCreated() +} + +func TestSCIMMeta_GetLastModified(tt *testing.T) { + var zeroValue Timestamp + s := &SCIMMeta{LastModified: &zeroValue} + s.GetLastModified() + s = &SCIMMeta{} + s.GetLastModified() + s = nil + s.GetLastModified() +} + +func TestSCIMMeta_GetLocation(tt *testing.T) { + var zeroValue string + s := &SCIMMeta{Location: &zeroValue} + s.GetLocation() + s = &SCIMMeta{} + s.GetLocation() + s = nil + s.GetLocation() +} + +func TestSCIMMeta_GetResourceType(tt *testing.T) { + var zeroValue string + s := &SCIMMeta{ResourceType: &zeroValue} + s.GetResourceType() + s = &SCIMMeta{} + s.GetResourceType() + s = nil + s.GetResourceType() +} + +func TestSCIMProvisionedIdentities_GetItemsPerPage(tt *testing.T) { + var zeroValue int + s := &SCIMProvisionedIdentities{ItemsPerPage: &zeroValue} + s.GetItemsPerPage() + s = &SCIMProvisionedIdentities{} + s.GetItemsPerPage() + s = nil + s.GetItemsPerPage() +} + +func TestSCIMProvisionedIdentities_GetStartIndex(tt *testing.T) { + var zeroValue int + s := &SCIMProvisionedIdentities{StartIndex: &zeroValue} + s.GetStartIndex() + s = &SCIMProvisionedIdentities{} + s.GetStartIndex() + s = nil + s.GetStartIndex() +} + +func TestSCIMProvisionedIdentities_GetTotalResults(tt *testing.T) { + var zeroValue int + s := &SCIMProvisionedIdentities{TotalResults: &zeroValue} + s.GetTotalResults() + s = &SCIMProvisionedIdentities{} + s.GetTotalResults() + s = nil + s.GetTotalResults() +} + func TestSCIMUserAttributes_GetActive(tt *testing.T) { var zeroValue bool s := &SCIMUserAttributes{Active: &zeroValue} @@ -19381,6 +19451,23 @@ func TestSCIMUserAttributes_GetExternalID(tt *testing.T) { s.GetExternalID() } +func TestSCIMUserAttributes_GetID(tt *testing.T) { + var zeroValue string + s := &SCIMUserAttributes{ID: &zeroValue} + s.GetID() + s = &SCIMUserAttributes{} + s.GetID() + s = nil + s.GetID() +} + +func TestSCIMUserAttributes_GetMeta(tt *testing.T) { + s := &SCIMUserAttributes{} + s.GetMeta() + s = nil + s.GetMeta() +} + func TestSCIMUserEmail_GetPrimary(tt *testing.T) { var zeroValue bool s := &SCIMUserEmail{Primary: &zeroValue} diff --git a/github/scim.go b/github/scim.go index 9462fb6ba6..fb4bcd64cd 100644 --- a/github/scim.go +++ b/github/scim.go @@ -29,6 +29,9 @@ type SCIMUserAttributes struct { ExternalID *string `json:"externalId,omitempty"` // (Optional.) Groups []string `json:"groups,omitempty"` // (Optional.) Active *bool `json:"active,omitempty"` // (Optional.) + // Only populated as a result of calling ListSCIMProvisionedIdentitiesOptions or GetSCIMProvisioningInfoForUser: + ID *string `json:"id,omitempty"` + Meta *SCIMMeta `json:"meta,omitempty"` } // SCIMUserName represents SCIM user information. @@ -45,6 +48,23 @@ type SCIMUserEmail struct { Type *string `json:"type,omitempty"` // (Optional.) } +// SCIMMeta represents metadata about the SCIM resource. +type SCIMMeta struct { + ResourceType *string `json:"resourceType,omitempty"` + Created *Timestamp `json:"created,omitempty"` + LastModified *Timestamp `json:"lastModified,omitempty"` + Location *string `json:"location,omitempty"` +} + +// SCIMProvisionedIdentities represents the result of calling ListSCIMProvisionedIdentities. +type SCIMProvisionedIdentities struct { + Schemas []string `json:"schemas,omitempty"` + TotalResults *int `json:"totalResults,omitempty"` + ItemsPerPage *int `json:"itemsPerPage,omitempty"` + StartIndex *int `json:"startIndex,omitempty"` + Resources []*SCIMUserAttributes `json:"Resources,omitempty"` +} + // ListSCIMProvisionedIdentitiesOptions represents options for ListSCIMProvisionedIdentities. // // Github API docs: https://docs.github.com/en/rest/scim#list-scim-provisioned-identities--parameters @@ -62,17 +82,22 @@ type ListSCIMProvisionedIdentitiesOptions struct { // ListSCIMProvisionedIdentities lists SCIM provisioned identities. // // GitHub API docs: https://docs.github.com/en/rest/scim#list-scim-provisioned-identities -func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org string, opts *ListSCIMProvisionedIdentitiesOptions) (*Response, error) { +func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org string, opts *ListSCIMProvisionedIdentitiesOptions) (*SCIMProvisionedIdentities, *Response, error) { u := fmt.Sprintf("scim/v2/organizations/%v/Users", org) u, err := addOptions(u, opts) if err != nil { - return nil, err + return nil, nil, err } req, err := s.client.NewRequest("GET", u, nil) if err != nil { - return nil, err + return nil, nil, err } - return s.client.Do(ctx, req, nil) + identities := new(SCIMProvisionedIdentities) + resp, err := s.client.Do(ctx, req, identities) + if err != nil { + return nil, resp, err + } + return identities, resp, nil } // ProvisionAndInviteSCIMUser provisions organization membership for a user, and sends an activation email to the email address. @@ -94,13 +119,18 @@ func (s *SCIMService) ProvisionAndInviteSCIMUser(ctx context.Context, org string // GetSCIMProvisioningInfoForUser returns SCIM provisioning information for a user. // // GitHub API docs: https://docs.github.com/en/rest/scim#supported-scim-user-attributes -func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, scimUserID string) (*Response, error) { +func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, scimUserID string) (*SCIMUserAttributes, *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 nil, nil, err } - return s.client.Do(ctx, req, nil) + user := new(SCIMUserAttributes) + resp, err := s.client.Do(ctx, req, &user) + if err != nil { + return nil, resp, err + } + return user, resp, nil } // UpdateProvisionedOrgMembership updates a provisioned organization membership. diff --git a/github/scim_test.go b/github/scim_test.go index 328bc20974..7c6e14078b 100644 --- a/github/scim_test.go +++ b/github/scim_test.go @@ -9,6 +9,9 @@ import ( "context" "net/http" "testing" + "time" + + "github.com/google/go-cmp/cmp" ) func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) { @@ -18,23 +21,102 @@ func TestSCIMService_ListSCIMProvisionedIdentities(t *testing.T) { mux.HandleFunc("/scim/v2/organizations/o/Users", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{ + "schemas": [ + "urn:ietf:params:scim:api:messages:2.0:ListResponse" + ], + "totalResults": 1, + "itemsPerPage": 1, + "startIndex": 1, + "Resources": [ + { + "schemas": [ + "urn:ietf:params:scim:schemas:core:2.0:User" + ], + "id": "5fc0c238-1112-11e8-8e45-920c87bdbd75", + "externalId": "00u1dhhb1fkIGP7RL1d8", + "userName": "octocat@github.com", + "displayName": "Mona Octocat", + "name": { + "givenName": "Mona", + "familyName": "Octocat", + "formatted": "Mona Octocat" + }, + "emails": [ + { + "value": "octocat@github.com", + "primary": true + } + ], + "active": true, + "meta": { + "resourceType": "User", + "created": "2018-02-13T15:05:24.000-00:00", + "lastModified": "2018-02-13T15:05:24.000-00:00", + "location": "https://api.github.com/scim/v2/organizations/octo-org/Users/5fc0c238-1112-11e8-8e45-920c87bdbd75" + } + } + ] + }`)) }) ctx := context.Background() opts := &ListSCIMProvisionedIdentitiesOptions{} - _, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts) + identities, _, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts) if err != nil { t.Errorf("SCIM.ListSCIMProvisionedIdentities returned error: %v", err) } + date := Timestamp{time.Date(2018, time.February, 13, 15, 5, 24, 0, time.UTC)} + want := SCIMProvisionedIdentities{ + Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:ListResponse"}, + TotalResults: Int(1), + ItemsPerPage: Int(1), + StartIndex: Int(1), + Resources: []*SCIMUserAttributes{ + { + ID: String("5fc0c238-1112-11e8-8e45-920c87bdbd75"), + Meta: &SCIMMeta{ + ResourceType: String("User"), + Created: &date, + LastModified: &date, + Location: String("https://api.github.com/scim/v2/organizations/octo-org/Users/5fc0c238-1112-11e8-8e45-920c87bdbd75"), + }, + UserName: "octocat@github.com", + Name: SCIMUserName{ + GivenName: "Mona", + FamilyName: "Octocat", + Formatted: String("Mona Octocat"), + }, + DisplayName: String("Mona Octocat"), + Emails: []*SCIMUserEmail{ + { + Value: "octocat@github.com", + Primary: Bool(true), + }, + }, + Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"}, + ExternalID: String("00u1dhhb1fkIGP7RL1d8"), + Groups: nil, + Active: Bool(true), + }, + }, + } + + if !cmp.Equal(identities, &want) { + diff := cmp.Diff(identities, want) + t.Errorf("SCIM.ListSCIMProvisionedIdentities returned %+v, want %+v: diff %+v", identities, want, diff) + } + const methodName = "ListSCIMProvisionedIdentities" testBadOptions(t, methodName, func() (err error) { - _, err = client.SCIM.ListSCIMProvisionedIdentities(ctx, "\n", opts) + _, _, err = client.SCIM.ListSCIMProvisionedIdentities(ctx, "\n", opts) return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - return client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts) + _, r, err := client.SCIM.ListSCIMProvisionedIdentities(ctx, "o", opts) + return r, err }) } @@ -83,22 +165,89 @@ func TestSCIMService_GetSCIMProvisioningInfoForUser(t *testing.T) { mux.HandleFunc("/scim/v2/organizations/o/Users/123", func(w http.ResponseWriter, r *http.Request) { testMethod(t, r, "GET") w.WriteHeader(http.StatusOK) + _, _ = w.Write([]byte(`{ + "schemas": [ + "urn:ietf:params:scim:schemas:core:2.0:User" + ], + "id": "edefdfedf-050c-11e7-8d32", + "externalId": "a7d0f98382", + "userName": "mona.octocat@okta.example.com", + "displayName": "Mona Octocat", + "name": { + "givenName": "Mona", + "familyName": "Octocat", + "formatted": "Mona Octocat" + }, + "emails": [ + { + "value": "mona.octocat@okta.example.com", + "primary": true + }, + { + "value": "mona@octocat.github.com" + } + ], + "active": true, + "meta": { + "resourceType": "User", + "created": "2017-03-09T16:11:13-00:00", + "lastModified": "2017-03-09T16:11:13-00:00", + "location": "https://api.github.com/scim/v2/organizations/octo-org/Users/edefdfedf-050c-11e7-8d32" + } + }`)) }) ctx := context.Background() - _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123") + user, _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123") if err != nil { t.Errorf("SCIM.GetSCIMProvisioningInfoForUser returned error: %v", err) } + date := Timestamp{time.Date(2017, time.March, 9, 16, 11, 13, 0, time.UTC)} + want := SCIMUserAttributes{ + ID: String("edefdfedf-050c-11e7-8d32"), + Meta: &SCIMMeta{ + ResourceType: String("User"), + Created: &date, + LastModified: &date, + Location: String("https://api.github.com/scim/v2/organizations/octo-org/Users/edefdfedf-050c-11e7-8d32"), + }, + UserName: "mona.octocat@okta.example.com", + Name: SCIMUserName{ + GivenName: "Mona", + FamilyName: "Octocat", + Formatted: String("Mona Octocat"), + }, + DisplayName: String("Mona Octocat"), + Emails: []*SCIMUserEmail{ + { + Value: "mona.octocat@okta.example.com", + Primary: Bool(true), + }, + { + Value: "mona@octocat.github.com", + }, + }, + Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"}, + ExternalID: String("a7d0f98382"), + Groups: nil, + Active: Bool(true), + } + + if !cmp.Equal(user, &want) { + diff := cmp.Diff(user, want) + t.Errorf("SCIM.ListSCIMProvisionedIdentities returned %+v, want %+v: diff %+v", user, want, diff) + } + const methodName = "GetSCIMProvisioningInfoForUser" testBadOptions(t, methodName, func() error { - _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "\n", "123") + _, _, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "\n", "123") return err }) testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { - return client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123") + _, r, err := client.SCIM.GetSCIMProvisioningInfoForUser(ctx, "o", "123") + return r, err }) } From c2e20da969217f856570d1d88388f3539180f630 Mon Sep 17 00:00:00 2001 From: tenyo Date: Wed, 21 Sep 2022 16:42:23 -0400 Subject: [PATCH 2/3] fixed formatting --- github/scim.go | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/github/scim.go b/github/scim.go index fb4bcd64cd..67195468a4 100644 --- a/github/scim.go +++ b/github/scim.go @@ -88,15 +88,18 @@ func (s *SCIMService) ListSCIMProvisionedIdentities(ctx context.Context, org str if err != nil { return nil, nil, err } + req, err := s.client.NewRequest("GET", u, nil) if err != nil { return nil, nil, err } + identities := new(SCIMProvisionedIdentities) resp, err := s.client.Do(ctx, req, identities) if err != nil { return nil, resp, err } + return identities, resp, nil } @@ -125,11 +128,13 @@ func (s *SCIMService) GetSCIMProvisioningInfoForUser(ctx context.Context, org, s if err != nil { return nil, nil, err } + user := new(SCIMUserAttributes) resp, err := s.client.Do(ctx, req, &user) if err != nil { return nil, resp, err } + return user, resp, nil } From 5def02a6a3fe7c0e598b9a85b6b6528f25883f88 Mon Sep 17 00:00:00 2001 From: tenyo Date: Wed, 21 Sep 2022 17:09:05 -0400 Subject: [PATCH 3/3] more blank lines --- github/scim.go | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/github/scim.go b/github/scim.go index 67195468a4..70f81ed14e 100644 --- a/github/scim.go +++ b/github/scim.go @@ -112,10 +112,12 @@ func (s *SCIMService) ProvisionAndInviteSCIMUser(ctx context.Context, org string 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) } @@ -147,10 +149,12 @@ func (s *SCIMService) UpdateProvisionedOrgMembership(ctx context.Context, org, s 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) } @@ -178,10 +182,12 @@ func (s *SCIMService) UpdateAttributeForSCIMUser(ctx context.Context, org, scimU 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) } @@ -194,5 +200,6 @@ func (s *SCIMService) DeleteSCIMUserFromOrg(ctx context.Context, org, scimUserID if err != nil { return nil, err } + return s.client.Do(ctx, req, nil) }