Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
54 changes: 49 additions & 5 deletions github/enterprise_scim.go
Original file line number Diff line number Diff line change
Expand Up @@ -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).
}

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.

Loading