Skip to content
52 changes: 48 additions & 4 deletions github/enterprise_scim.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,16 +33,16 @@ 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.
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).
}

Expand Down Expand Up @@ -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
}
222 changes: 213 additions & 9 deletions github/enterprise_scim_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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},
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"),
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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"
Expand Down Expand Up @@ -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{
Expand All @@ -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"
Expand Down Expand Up @@ -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"
Expand All @@ -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
})
}
8 changes: 8 additions & 0 deletions github/github-accessors.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

11 changes: 11 additions & 0 deletions github/github-accessors_test.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.