Skip to content
This repository was archived by the owner on Feb 3, 2026. It is now read-only.

Commit e8b48db

Browse files
authored
fix: add scim schemas (#157)
fix bad SCIM request error
1 parent a0d56bd commit e8b48db

File tree

3 files changed

+62
-4
lines changed

3 files changed

+62
-4
lines changed

pkg/github/enterpriseuserwriter_test.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -87,6 +87,7 @@ func TestEnterpriseUserWriter_SetMembers(t *testing.T) {
8787
ID: github.String("scim-id-user.new"),
8888
UserName: "user.new",
8989
Active: github.Bool(true),
90+
Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
9091
},
9192
},
9293
},
@@ -109,6 +110,7 @@ func TestEnterpriseUserWriter_SetMembers(t *testing.T) {
109110
ID: github.String("scim-id-user.one"),
110111
UserName: "user.one",
111112
Active: github.Bool(true),
113+
Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
112114
},
113115
},
114116
},
@@ -243,6 +245,8 @@ func TestEnterpriseUserWriter_SetMembers(t *testing.T) {
243245
"scim-id-user.one": {
244246
ID: github.String("scim-id-user.one"),
245247
UserName: "user.one",
248+
Active: github.Bool(true),
249+
Schemas: []string{"urn:ietf:params:scim:schemas:core:2.0:User"},
246250
},
247251
},
248252
},

pkg/github/scim.go

Lines changed: 39 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,21 @@ type SCIMClient struct {
3737
baseURL *url.URL
3838
}
3939

40+
// scimPatchOp represents a single SCIM patch operation.
41+
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2
42+
type scimPatchOp struct {
43+
Op string `json:"op"`
44+
Path string `json:"path,omitempty"`
45+
Value any `json:"value,omitempty"`
46+
}
47+
48+
// scimPatchPayload is the body of a SCIM PATCH request.
49+
// https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2
50+
type scimPatchPayload struct {
51+
Schemas []string `json:"schemas"`
52+
Operations []scimPatchOp `json:"Operations"`
53+
}
54+
4055
// NewSCIMClient creates a new client for the GHES SCIM API.
4156
func NewSCIMClient(httpClient *http.Client, baseURL string) (*SCIMClient, error) {
4257
u, err := url.Parse(strings.TrimSuffix(baseURL, "/") + ghesSCIMURLPath)
@@ -79,6 +94,9 @@ func (c *SCIMClient) ListUsers(ctx context.Context) (map[string]*github.SCIMUser
7994
// CreateUser provisions a new user.
8095
func (c *SCIMClient) CreateUser(ctx context.Context, user *github.SCIMUserAttributes) (*github.SCIMUserAttributes, *github.Response, error) {
8196
path := "Users"
97+
// Schema for POST: https://datatracker.ietf.org/doc/html/rfc7644#section-3.3
98+
user.Schemas = append(user.Schemas, "urn:ietf:params:scim:schemas:core:2.0:User")
99+
user.Active = github.Bool(true)
82100
var createdUser github.SCIMUserAttributes
83101
resp, err := c.do(ctx, http.MethodPost, c.baseURL.ResolveReference(&url.URL{Path: path}).String(), user, &createdUser)
84102
if err != nil {
@@ -101,6 +119,8 @@ func (c *SCIMClient) GetUser(ctx context.Context, scimID string) (*github.SCIMUs
101119
// UpdateUser updates a user's attributes.
102120
func (c *SCIMClient) UpdateUser(ctx context.Context, scimID string, user *github.SCIMUserAttributes) (*github.SCIMUserAttributes, *github.Response, error) {
103121
path := fmt.Sprintf("Users/%s", scimID)
122+
// Schema for PUT: https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.1
123+
user.Schemas = append(user.Schemas, "urn:ietf:params:scim:schemas:core:2.0:User")
104124
var updatedUser github.SCIMUserAttributes
105125
resp, err := c.do(ctx, http.MethodPut, c.baseURL.ResolveReference(&url.URL{Path: path}).String(), user, &updatedUser)
106126
if err != nil {
@@ -113,7 +133,16 @@ func (c *SCIMClient) UpdateUser(ctx context.Context, scimID string, user *github
113133
// https://docs.github.com/en/enterprise-server@3.17/admin/managing-iam/provisioning-user-accounts-with-scim/provisioning-users-and-groups-with-scim-using-the-rest-api#soft-deprovisioning-users-with-the-rest-api
114134
func (c *SCIMClient) DeactivateUser(ctx context.Context, scimID string) (*github.SCIMUserAttributes, *github.Response, error) {
115135
path := fmt.Sprintf("Users/%s", scimID)
116-
payload := &github.SCIMUserAttributes{Active: github.Bool(false)}
136+
// Schema for PATCH: https://datatracker.ietf.org/doc/html/rfc7644#section-3.5.2
137+
payload := &scimPatchPayload{
138+
Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
139+
Operations: []scimPatchOp{
140+
{
141+
Op: "replace",
142+
Value: map[string]bool{"active": false},
143+
},
144+
},
145+
}
117146
var deactivatedUser github.SCIMUserAttributes
118147
resp, err := c.do(ctx, http.MethodPatch, c.baseURL.ResolveReference(&url.URL{Path: path}).String(), payload, &deactivatedUser)
119148
if err != nil {
@@ -126,7 +155,15 @@ func (c *SCIMClient) DeactivateUser(ctx context.Context, scimID string) (*github
126155
// https://docs.github.com/en/enterprise-server@3.17/admin/managing-iam/provisioning-user-accounts-with-scim/deprovisioning-and-reinstating-users#reinstating-a-user-account-that-was-soft-deprovisioned
127156
func (c *SCIMClient) ReactivateUser(ctx context.Context, scimID string) (*github.SCIMUserAttributes, *github.Response, error) {
128157
path := fmt.Sprintf("Users/%s", scimID)
129-
payload := &github.SCIMUserAttributes{Active: github.Bool(true)}
158+
payload := &scimPatchPayload{
159+
Schemas: []string{"urn:ietf:params:scim:api:messages:2.0:PatchOp"},
160+
Operations: []scimPatchOp{
161+
{
162+
Op: "replace",
163+
Value: map[string]bool{"active": true},
164+
},
165+
},
166+
}
130167
var reactivatedUser github.SCIMUserAttributes
131168
resp, err := c.do(ctx, http.MethodPatch, c.baseURL.ResolveReference(&url.URL{Path: path}).String(), payload, &reactivatedUser)
132169
if err != nil {

pkg/github/scim_test.go

Lines changed: 19 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -111,12 +111,22 @@ func fakeEnterprise(t *testing.T, data *EnterpriseUserData) *httptest.Server {
111111
http.Error(w, "not found", http.StatusNotFound)
112112
return
113113
}
114-
var patch github.SCIMUserAttributes
114+
var patch scimPatchPayload
115115
if err := json.NewDecoder(r.Body).Decode(&patch); err != nil {
116116
http.Error(w, err.Error(), http.StatusBadRequest)
117117
return
118118
}
119-
user.Active = patch.Active
119+
if len(patch.Operations) > 0 {
120+
op := patch.Operations[0]
121+
if op.Op == "replace" {
122+
if value, ok := op.Value.(map[string]interface{}); ok {
123+
if active, ok := value["active"].(bool); ok {
124+
user.Active = github.Bool(active)
125+
}
126+
}
127+
}
128+
}
129+
120130
if err := json.NewEncoder(w).Encode(user); err != nil {
121131
t.Fatalf("failed to encode create response: %v", err)
122132
}
@@ -217,6 +227,9 @@ func TestSCIMClient_UpdateUser(t *testing.T) {
217227
wantFinalUser: &github.SCIMUserAttributes{
218228
ID: github.String("id1"),
219229
Name: github.SCIMUserName{GivenName: "New", FamilyName: "Name"},
230+
Schemas: []string{
231+
"urn:ietf:params:scim:schemas:core:2.0:User",
232+
},
220233
},
221234
wantServerCount: 1,
222235
},
@@ -327,6 +340,10 @@ func TestSCIMClient_CreateUser(t *testing.T) {
327340
wantUserOnServer: &github.SCIMUserAttributes{
328341
ID: github.String("scim-id-new.user"),
329342
UserName: "new.user",
343+
Schemas: []string{
344+
"urn:ietf:params:scim:schemas:core:2.0:User",
345+
},
346+
Active: github.Bool(true),
330347
},
331348
wantServerUserCount: 1,
332349
},

0 commit comments

Comments
 (0)