From c52e7409171c66db0253999be65927da36766b1b Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 25 Oct 2024 11:23:03 -0500 Subject: [PATCH 1/2] feat(enterprise): support bearer tokens in SCIM authentication --- enterprise/coderd/scim.go | 5 +++ enterprise/coderd/scim_test.go | 62 ++++++++++++++++++++++++++++++++++ 2 files changed, 67 insertions(+) diff --git a/enterprise/coderd/scim.go b/enterprise/coderd/scim.go index 45390b6014a6a..5db1ed52bbc42 100644 --- a/enterprise/coderd/scim.go +++ b/enterprise/coderd/scim.go @@ -35,8 +35,13 @@ func (api *API) scimEnabledMW(next http.Handler) http.Handler { } func (api *API) scimVerifyAuthHeader(r *http.Request) bool { + bearer := []byte("Bearer ") hdr := []byte(r.Header.Get("Authorization")) + if len(hdr) >= len(bearer) && subtle.ConstantTimeCompare(hdr[:len(bearer)], bearer) == 1 { + hdr = hdr[len(bearer):] + } + return len(api.SCIMAPIKey) != 0 && subtle.ConstantTimeCompare(hdr, api.SCIMAPIKey) == 1 } diff --git a/enterprise/coderd/scim_test.go b/enterprise/coderd/scim_test.go index 8d65d9bb34531..43593b8878825 100644 --- a/enterprise/coderd/scim_test.go +++ b/enterprise/coderd/scim_test.go @@ -56,6 +56,12 @@ func setScimAuth(key []byte) func(*http.Request) { } } +func setScimAuthBearer(key []byte) func(*http.Request) { + return func(r *http.Request) { + r.Header.Set("Authorization", "Bearer"+string(key)) + } +} + //nolint:gocritic // SCIM authenticates via a special header and bypasses internal RBAC. func TestScim(t *testing.T) { t.Parallel() @@ -163,6 +169,62 @@ func TestScim(t *testing.T) { require.Empty(t, notifyEnq.Sent) }) + t.Run("OK_Bearer", func(t *testing.T) { + t.Parallel() + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + // given + scimAPIKey := []byte("hi") + mockAudit := audit.NewMock() + notifyEnq := &testutil.FakeNotificationsEnqueuer{} + client, _ := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + Auditor: mockAudit, + NotificationsEnqueuer: notifyEnq, + }, + SCIMAPIKey: scimAPIKey, + AuditLogging: true, + LicenseOptions: &coderdenttest.LicenseOptions{ + AccountID: "coolin", + Features: license.Features{ + codersdk.FeatureSCIM: 1, + codersdk.FeatureAuditLog: 1, + }, + }, + }) + mockAudit.ResetLogs() + + // when + sUser := makeScimUser(t) + res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuthBearer(scimAPIKey)) + require.NoError(t, err) + defer res.Body.Close() + require.Equal(t, http.StatusOK, res.StatusCode) + + // then + // Expect audit logs + aLogs := mockAudit.AuditLogs() + require.Len(t, aLogs, 1) + af := map[string]string{} + err = json.Unmarshal([]byte(aLogs[0].AdditionalFields), &af) + require.NoError(t, err) + assert.Equal(t, coderd.SCIMAuditAdditionalFields, af) + assert.Equal(t, database.AuditActionCreate, aLogs[0].Action) + + // Expect users exposed over API + userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value}) + require.NoError(t, err) + require.Len(t, userRes.Users, 1) + assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email) + assert.Equal(t, sUser.UserName, userRes.Users[0].Username) + assert.Len(t, userRes.Users[0].OrganizationIDs, 1) + + // Expect zero notifications (SkipNotifications = true) + require.Empty(t, notifyEnq.Sent) + }) + t.Run("OKNoDefault", func(t *testing.T) { t.Parallel() From 35483f2c38776c9d27fc768a8588b53ad67fc180 Mon Sep 17 00:00:00 2001 From: Colin Adler Date: Fri, 25 Oct 2024 11:39:53 -0500 Subject: [PATCH 2/2] fixup! feat(enterprise): support bearer tokens in SCIM authentication --- enterprise/coderd/scim_test.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/enterprise/coderd/scim_test.go b/enterprise/coderd/scim_test.go index 43593b8878825..c45ded27d6226 100644 --- a/enterprise/coderd/scim_test.go +++ b/enterprise/coderd/scim_test.go @@ -58,7 +58,7 @@ func setScimAuth(key []byte) func(*http.Request) { func setScimAuthBearer(key []byte) func(*http.Request) { return func(r *http.Request) { - r.Header.Set("Authorization", "Bearer"+string(key)) + r.Header.Set("Authorization", "Bearer "+string(key)) } }