Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 487b37b

Browse files
authored
feat(enterprise): support bearer tokens in SCIM authentication (coder#15233)
1 parent 0dd942e commit 487b37b

File tree

2 files changed

+67
-0
lines changed

2 files changed

+67
-0
lines changed

enterprise/coderd/scim.go

+5
Original file line numberDiff line numberDiff line change
@@ -35,8 +35,13 @@ func (api *API) scimEnabledMW(next http.Handler) http.Handler {
3535
}
3636

3737
func (api *API) scimVerifyAuthHeader(r *http.Request) bool {
38+
bearer := []byte("Bearer ")
3839
hdr := []byte(r.Header.Get("Authorization"))
3940

41+
if len(hdr) >= len(bearer) && subtle.ConstantTimeCompare(hdr[:len(bearer)], bearer) == 1 {
42+
hdr = hdr[len(bearer):]
43+
}
44+
4045
return len(api.SCIMAPIKey) != 0 && subtle.ConstantTimeCompare(hdr, api.SCIMAPIKey) == 1
4146
}
4247

enterprise/coderd/scim_test.go

+62
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,12 @@ func setScimAuth(key []byte) func(*http.Request) {
5656
}
5757
}
5858

59+
func setScimAuthBearer(key []byte) func(*http.Request) {
60+
return func(r *http.Request) {
61+
r.Header.Set("Authorization", "Bearer "+string(key))
62+
}
63+
}
64+
5965
//nolint:gocritic // SCIM authenticates via a special header and bypasses internal RBAC.
6066
func TestScim(t *testing.T) {
6167
t.Parallel()
@@ -163,6 +169,62 @@ func TestScim(t *testing.T) {
163169
require.Empty(t, notifyEnq.Sent)
164170
})
165171

172+
t.Run("OK_Bearer", func(t *testing.T) {
173+
t.Parallel()
174+
175+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
176+
defer cancel()
177+
178+
// given
179+
scimAPIKey := []byte("hi")
180+
mockAudit := audit.NewMock()
181+
notifyEnq := &testutil.FakeNotificationsEnqueuer{}
182+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
183+
Options: &coderdtest.Options{
184+
Auditor: mockAudit,
185+
NotificationsEnqueuer: notifyEnq,
186+
},
187+
SCIMAPIKey: scimAPIKey,
188+
AuditLogging: true,
189+
LicenseOptions: &coderdenttest.LicenseOptions{
190+
AccountID: "coolin",
191+
Features: license.Features{
192+
codersdk.FeatureSCIM: 1,
193+
codersdk.FeatureAuditLog: 1,
194+
},
195+
},
196+
})
197+
mockAudit.ResetLogs()
198+
199+
// when
200+
sUser := makeScimUser(t)
201+
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuthBearer(scimAPIKey))
202+
require.NoError(t, err)
203+
defer res.Body.Close()
204+
require.Equal(t, http.StatusOK, res.StatusCode)
205+
206+
// then
207+
// Expect audit logs
208+
aLogs := mockAudit.AuditLogs()
209+
require.Len(t, aLogs, 1)
210+
af := map[string]string{}
211+
err = json.Unmarshal([]byte(aLogs[0].AdditionalFields), &af)
212+
require.NoError(t, err)
213+
assert.Equal(t, coderd.SCIMAuditAdditionalFields, af)
214+
assert.Equal(t, database.AuditActionCreate, aLogs[0].Action)
215+
216+
// Expect users exposed over API
217+
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})
218+
require.NoError(t, err)
219+
require.Len(t, userRes.Users, 1)
220+
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
221+
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
222+
assert.Len(t, userRes.Users[0].OrganizationIDs, 1)
223+
224+
// Expect zero notifications (SkipNotifications = true)
225+
require.Empty(t, notifyEnq.Sent)
226+
})
227+
166228
t.Run("OKNoDefault", func(t *testing.T) {
167229
t.Parallel()
168230

0 commit comments

Comments
 (0)