diff --git a/github/enterprise_scim.go b/github/enterprise_scim.go index 3243841a88c..8503af0bc3a 100644 --- a/github/enterprise_scim.go +++ b/github/enterprise_scim.go @@ -32,17 +32,17 @@ const SCIMSchemasURINamespacesPatchOp = "urn:ietf:params:scim:api:messages:2.0:P type SCIMEnterpriseGroupAttributes struct { DisplayName *string `json:"displayName,omitempty"` // Human-readable name for a group. Members []*SCIMEnterpriseDisplayReference `json:"members,omitempty"` // List of members who are assigned to the group in SCIM provider - ExternalID *string `json:"externalId,omitempty"` // This identifier is generated by a SCIM provider. Must be unique per user. + ExternalID *string `json:"externalId,omitempty"` // This identifier is generated by a SCIM provider. Must be unique per group. + Schemas []string `json:"schemas,omitempty"` // The URIs that are used to indicate the namespaces of the SCIM schemas. // Bellow: Only populated as a result of calling UpdateSCIMGroupAttribute: - Schemas []string `json:"schemas,omitempty"` // The URIs that are used to indicate the namespaces of the SCIM schemas. - ID *string `json:"id,omitempty"` // The internally generated id for the group object. - Meta *SCIMEnterpriseMeta `json:"meta,omitempty"` // The metadata associated with the creation/updates to the group. + ID *string `json:"id,omitempty"` // The internally generated id for the group object. + Meta *SCIMEnterpriseMeta `json:"meta,omitempty"` // The metadata associated with the creation/updates to the group. } // SCIMEnterpriseDisplayReference represents a JSON SCIM (System for Cross-domain Identity Management) resource reference. type SCIMEnterpriseDisplayReference struct { Value string `json:"value"` // The local unique identifier for the member (e.g., user ID or group ID). - Ref string `json:"$ref"` // The URI reference to the member resource (e.g., https://api.github.com/scim/v2/Users/{id}). + Ref *string `json:"$ref,omitempty"` // The URI reference to the member resource (e.g., https://api.github.com/scim/v2/Users/{id}). Display *string `json:"display,omitempty"` // The display name associated with the member (e.g., user name or group name). } @@ -257,3 +257,47 @@ func (s *EnterpriseService) UpdateSCIMUserAttribute(ctx context.Context, enterpr return user, resp, nil } + +// ProvisionSCIMGroup creates a SCIM group for an enterprise. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#provision-a-scim-enterprise-group +// +//meta:operation POST /scim/v2/enterprises/{enterprise}/Groups +func (s *EnterpriseService) ProvisionSCIMGroup(ctx context.Context, enterprise string, group SCIMEnterpriseGroupAttributes) (*SCIMEnterpriseGroupAttributes, *Response, error) { + u := fmt.Sprintf("scim/v2/enterprises/%v/Groups", enterprise) + req, err := s.client.NewRequest("POST", u, group) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", mediaTypeSCIM) + + groupProvisioned := new(SCIMEnterpriseGroupAttributes) + resp, err := s.client.Do(ctx, req, groupProvisioned) + if err != nil { + return nil, resp, err + } + + return groupProvisioned, resp, nil +} + +// ProvisionSCIMUser creates an external identity for a new SCIM enterprise user. +// +// GitHub API docs: https://docs.github.com/enterprise-cloud@latest/rest/enterprise-admin/scim#provision-a-scim-enterprise-user +// +//meta:operation POST /scim/v2/enterprises/{enterprise}/Users +func (s *EnterpriseService) ProvisionSCIMUser(ctx context.Context, enterprise string, user SCIMEnterpriseUserAttributes) (*SCIMEnterpriseUserAttributes, *Response, error) { + u := fmt.Sprintf("scim/v2/enterprises/%v/Users", enterprise) + req, err := s.client.NewRequest("POST", u, user) + if err != nil { + return nil, nil, err + } + req.Header.Set("Accept", mediaTypeSCIM) + + userProvisioned := new(SCIMEnterpriseUserAttributes) + resp, err := s.client.Do(ctx, req, userProvisioned) + if err != nil { + return nil, resp, err + } + + return userProvisioned, resp, nil +} diff --git a/github/enterprise_scim_test.go b/github/enterprise_scim_test.go index 4f65c27ff71..f55614c2135 100644 --- a/github/enterprise_scim_test.go +++ b/github/enterprise_scim_test.go @@ -26,7 +26,7 @@ func TestSCIMEnterpriseGroups_Marshal(t *testing.T) { DisplayName: Ptr("gn1"), Members: []*SCIMEnterpriseDisplayReference{{ Value: "idm1", - Ref: "https://api.github.com/scim/v2/enterprises/ee/Users/idm1", + Ref: Ptr("https://api.github.com/scim/v2/enterprises/ee/Users/idm1"), Display: Ptr("m1"), }}, Schemas: []string{SCIMSchemasURINamespacesGroups}, @@ -94,7 +94,7 @@ func TestSCIMEnterpriseUsers_Marshal(t *testing.T) { UserName: "un1", Groups: []*SCIMEnterpriseDisplayReference{{ Value: "idgn1", - Ref: "https://api.github.com/scim/v2/enterprises/ee/Groups/idgn1", + Ref: Ptr("https://api.github.com/scim/v2/enterprises/ee/Groups/idgn1"), Display: Ptr("gn1"), }}, ID: Ptr("idun1"), @@ -209,7 +209,7 @@ func TestSCIMEnterpriseGroupAttributes_Marshal(t *testing.T) { DisplayName: Ptr("dn"), Members: []*SCIMEnterpriseDisplayReference{{ Value: "v", - Ref: "r", + Ref: Ptr("r"), Display: Ptr("d"), }}, ExternalID: Ptr("eid"), @@ -346,14 +346,14 @@ func TestEnterpriseService_ListProvisionedSCIMGroups(t *testing.T) { ExternalID: Ptr("de88"), Members: []*SCIMEnterpriseDisplayReference{{ Value: "e7f9", - Ref: "https://api.github.com/scim/v2/enterprises/ee/Users/e7f9", + Ref: Ptr("https://api.github.com/scim/v2/enterprises/ee/Users/e7f9"), Display: Ptr("d1"), }}, }}, } if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Enterprise.ListProvisionedSCIMGroups diff mismatch (-want +got):\n%v", diff) + t.Fatalf("Enterprise.ListProvisionedSCIMGroups diff mismatch (-want +got):\n%v", diff) } const methodName = "ListProvisionedSCIMGroups" @@ -461,7 +461,7 @@ func TestEnterpriseService_ListProvisionedSCIMUsers(t *testing.T) { } if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Enterprise.ListProvisionedSCIMUsers diff mismatch (-want +got):\n%v", diff) + t.Fatalf("Enterprise.ListProvisionedSCIMUsers diff mismatch (-want +got):\n%v", diff) } const methodName = "ListProvisionedSCIMUsers" @@ -513,7 +513,7 @@ func TestEnterpriseService_UpdateSCIMGroupAttribute(t *testing.T) { DisplayName: Ptr("Employees"), Members: []*SCIMEnterpriseDisplayReference{{ Value: "879d", - Ref: "https://api.github.localhost/scim/v2/enterprises/ee/Users/879d", + Ref: Ptr("https://api.github.localhost/scim/v2/enterprises/ee/Users/879d"), Display: Ptr("User 1"), }}, Meta: &SCIMEnterpriseMeta{ @@ -538,7 +538,7 @@ func TestEnterpriseService_UpdateSCIMGroupAttribute(t *testing.T) { t.Fatalf("Enterprise.UpdateSCIMGroupAttribute returned unexpected error: %v", err) } if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Enterprise.UpdateSCIMGroupAttribute diff mismatch (-want +got):\n%v", diff) + t.Fatalf("Enterprise.UpdateSCIMGroupAttribute diff mismatch (-want +got):\n%v", diff) } const methodName = "UpdateSCIMGroupAttribute" @@ -643,7 +643,7 @@ func TestEnterpriseService_UpdateSCIMUserAttribute(t *testing.T) { t.Fatalf("Enterprise.UpdateSCIMUserAttribute returned unexpected error: %v", err) } if diff := cmp.Diff(want, got); diff != "" { - t.Errorf("Enterprise.UpdateSCIMUserAttribute diff mismatch (-want +got):\n%v", diff) + t.Fatalf("Enterprise.UpdateSCIMUserAttribute diff mismatch (-want +got):\n%v", diff) } const methodName = "UpdateSCIMUserAttribute" @@ -660,3 +660,207 @@ func TestEnterpriseService_UpdateSCIMUserAttribute(t *testing.T) { return resp, err }) } + +func TestEnterpriseService_ProvisionSCIMGroup(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/scim/v2/enterprises/ee/Groups", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeSCIM) + testBody(t, r, `{"displayName":"dn","members":[{"value":"879d","display":"d1"},{"value":"0db5","display":"d2"}],"externalId":"8aa1","schemas":["`+SCIMSchemasURINamespacesGroups+`"]}`+"\n") + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{ + "schemas": ["`+SCIMSchemasURINamespacesGroups+`"], + "id": "abcd", + "externalId": "8aa1", + "displayName": "dn", + "members": [ + { + "value": "879d", + "$ref": "https://api.github.localhost/scim/v2/enterprises/ee/Users/879d", + "display": "d1" + }, + { + "value": "0db5", + "$ref": "https://api.github.localhost/scim/v2/enterprises/ee/Users/0db5", + "display": "d2" + } + ], + "meta": { + "resourceType": "Group", + "created": `+referenceTimeStr+`, + "lastModified": `+referenceTimeStr+`, + "location": "https://api.github.localhost/scim/v2/enterprises/ee/Groups/abcd" + } + }`) + }) + want := &SCIMEnterpriseGroupAttributes{ + Schemas: []string{SCIMSchemasURINamespacesGroups}, + ID: Ptr("abcd"), + ExternalID: Ptr("8aa1"), + DisplayName: Ptr("dn"), + Members: []*SCIMEnterpriseDisplayReference{{ + Value: "879d", + Ref: Ptr("https://api.github.localhost/scim/v2/enterprises/ee/Users/879d"), + Display: Ptr("d1"), + }, { + Value: "0db5", + Ref: Ptr("https://api.github.localhost/scim/v2/enterprises/ee/Users/0db5"), + Display: Ptr("d2"), + }}, + Meta: &SCIMEnterpriseMeta{ + ResourceType: "Group", + Created: &Timestamp{referenceTime}, + LastModified: &Timestamp{referenceTime}, + Location: Ptr("https://api.github.localhost/scim/v2/enterprises/ee/Groups/abcd"), + }, + } + + ctx := t.Context() + input := SCIMEnterpriseGroupAttributes{ + Schemas: []string{SCIMSchemasURINamespacesGroups}, + ExternalID: Ptr("8aa1"), + DisplayName: Ptr("dn"), + Members: []*SCIMEnterpriseDisplayReference{{ + Value: "879d", + Display: Ptr("d1"), + }, { + Value: "0db5", + Display: Ptr("d2"), + }}, + } + got, _, err := client.Enterprise.ProvisionSCIMGroup(ctx, "ee", input) + if err != nil { + t.Fatalf("Enterprise.ProvisionSCIMGroup returned unexpected error: %v", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Fatalf("Enterprise.ProvisionSCIMGroup diff mismatch (-want +got):\n%v", diff) + } + + const methodName = "ProvisionSCIMGroup" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Enterprise.ProvisionSCIMGroup(ctx, "\n", SCIMEnterpriseGroupAttributes{}) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.ProvisionSCIMGroup(ctx, "ee", input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} + +func TestEnterpriseService_ProvisionSCIMUser(t *testing.T) { + t.Parallel() + client, mux, _ := setup(t) + + mux.HandleFunc("/scim/v2/enterprises/ee/Users", func(w http.ResponseWriter, r *http.Request) { + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeSCIM) + testBody(t, r, `{"displayName":"DOE John","name":{"givenName":"John","familyName":"Doe","formatted":"John Doe"},"userName":"e123","emails":[{"value":"john@email.com","primary":true,"type":"work"}],"roles":[{"value":"User","primary":false}],"externalId":"e123","active":true,"schemas":["`+SCIMSchemasURINamespacesUser+`"]}`+"\n") + w.WriteHeader(http.StatusCreated) + fmt.Fprint(w, `{ + "schemas": ["`+SCIMSchemasURINamespacesUser+`"], + "id": "7fce", + "externalId": "e123", + "active": true, + "userName": "e123", + "name": { + "formatted": "John Doe", + "familyName": "Doe", + "givenName": "John" + }, + "displayName": "DOE John", + "emails": [{ + "value": "john@email.com", + "type": "work", + "primary": true + }], + "roles": [{ + "value": "User", + "primary": false + }], + "meta": { + "resourceType": "User", + "created": `+referenceTimeStr+`, + "lastModified": `+referenceTimeStr+`, + "location": "https://api.github.localhost/scim/v2/enterprises/ee/Users/7fce" + } + }`) + }) + want := &SCIMEnterpriseUserAttributes{ + Schemas: []string{SCIMSchemasURINamespacesUser}, + ID: Ptr("7fce"), + ExternalID: "e123", + Active: true, + UserName: "e123", + DisplayName: "DOE John", + Name: &SCIMEnterpriseUserName{ + Formatted: Ptr("John Doe"), + FamilyName: "Doe", + GivenName: "John", + }, + Emails: []*SCIMEnterpriseUserEmail{{ + Value: "john@email.com", + Type: "work", + Primary: true, + }}, + Roles: []*SCIMEnterpriseUserRole{{ + Value: "User", + Primary: Ptr(false), + }}, + Meta: &SCIMEnterpriseMeta{ + ResourceType: "User", + Created: &Timestamp{referenceTime}, + LastModified: &Timestamp{referenceTime}, + Location: Ptr("https://api.github.localhost/scim/v2/enterprises/ee/Users/7fce"), + }, + } + + ctx := t.Context() + input := SCIMEnterpriseUserAttributes{ + Schemas: []string{SCIMSchemasURINamespacesUser}, + ExternalID: "e123", + Active: true, + UserName: "e123", + Name: &SCIMEnterpriseUserName{ + Formatted: Ptr("John Doe"), + FamilyName: "Doe", + GivenName: "John", + }, + DisplayName: "DOE John", + Emails: []*SCIMEnterpriseUserEmail{{ + Value: "john@email.com", + Type: "work", + Primary: true, + }}, + Roles: []*SCIMEnterpriseUserRole{{ + Value: "User", + Primary: Ptr(false), + }}, + } + got, _, err := client.Enterprise.ProvisionSCIMUser(ctx, "ee", input) + if err != nil { + t.Fatalf("Enterprise.ProvisionSCIMUser returned unexpected error: %v", err) + } + if diff := cmp.Diff(want, got); diff != "" { + t.Fatalf("Enterprise.ProvisionSCIMUser diff mismatch (-want +got):\n%v", diff) + } + + const methodName = "ProvisionSCIMUser" + testBadOptions(t, methodName, func() (err error) { + _, _, err = client.Enterprise.ProvisionSCIMUser(ctx, "\n", SCIMEnterpriseUserAttributes{}) + return err + }) + + testNewRequestAndDoFailure(t, methodName, client, func() (*Response, error) { + got, resp, err := client.Enterprise.ProvisionSCIMUser(ctx, "ee", input) + if got != nil { + t.Errorf("testNewRequestAndDoFailure %v = %#v, want nil", methodName, got) + } + return resp, err + }) +} diff --git a/github/github-accessors.go b/github/github-accessors.go index bb937fc4ab6..0e88a5235b4 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -26254,6 +26254,14 @@ func (s *SCIMEnterpriseDisplayReference) GetDisplay() string { return *s.Display } +// GetRef returns the Ref field if it's non-nil, zero value otherwise. +func (s *SCIMEnterpriseDisplayReference) GetRef() string { + if s == nil || s.Ref == nil { + return "" + } + return *s.Ref +} + // GetDisplayName returns the DisplayName field if it's non-nil, zero value otherwise. func (s *SCIMEnterpriseGroupAttributes) GetDisplayName() string { if s == nil || s.DisplayName == nil { diff --git a/github/github-accessors_test.go b/github/github-accessors_test.go index 76059c647c6..117b31e6697 100644 --- a/github/github-accessors_test.go +++ b/github/github-accessors_test.go @@ -33881,6 +33881,17 @@ func TestSCIMEnterpriseDisplayReference_GetDisplay(tt *testing.T) { s.GetDisplay() } +func TestSCIMEnterpriseDisplayReference_GetRef(tt *testing.T) { + tt.Parallel() + var zeroValue string + s := &SCIMEnterpriseDisplayReference{Ref: &zeroValue} + s.GetRef() + s = &SCIMEnterpriseDisplayReference{} + s.GetRef() + s = nil + s.GetRef() +} + func TestSCIMEnterpriseGroupAttributes_GetDisplayName(tt *testing.T) { tt.Parallel() var zeroValue string