Skip to content

Commit 487b37b

Browse files
authored
feat(enterprise): support bearer tokens in SCIM authentication (#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

Lines changed: 5 additions & 0 deletions
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

Lines changed: 62 additions & 0 deletions
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)
pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy