Skip to content

Commit be2e6f4

Browse files
coadlerbpmct
authored andcommitted
fix(enterprise): ensure creating a SCIM user is idempotent (#8730)
1 parent 201ac99 commit be2e6f4

File tree

2 files changed

+53
-1
lines changed

2 files changed

+53
-1
lines changed

enterprise/coderd/scim.go

Lines changed: 20 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package coderd
22

33
import (
44
"crypto/subtle"
5+
"database/sql"
56
"encoding/json"
67
"net/http"
78

@@ -11,6 +12,7 @@ import (
1112
scimjson "github.com/imulab/go-scim/pkg/v2/json"
1213
"github.com/imulab/go-scim/pkg/v2/service"
1314
"github.com/imulab/go-scim/pkg/v2/spec"
15+
"golang.org/x/xerrors"
1416

1517
agpl "github.com/coder/coder/coderd"
1618
"github.com/coder/coder/coderd/database"
@@ -152,6 +154,23 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
152154
return
153155
}
154156

157+
//nolint:gocritic
158+
user, err := api.Database.GetUserByEmailOrUsername(dbauthz.AsSystemRestricted(ctx), database.GetUserByEmailOrUsernameParams{
159+
Email: email,
160+
Username: sUser.UserName,
161+
})
162+
if err != nil && !xerrors.Is(err, sql.ErrNoRows) {
163+
_ = handlerutil.WriteError(rw, err)
164+
return
165+
}
166+
if err == nil {
167+
sUser.ID = user.ID.String()
168+
sUser.UserName = user.Username
169+
170+
httpapi.Write(ctx, rw, http.StatusOK, sUser)
171+
return
172+
}
173+
155174
// The username is a required property in Coder. We make a best-effort
156175
// attempt at using what the claims provide, but if that fails we will
157176
// generate a random username.
@@ -182,7 +201,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
182201
}
183202

184203
//nolint:gocritic // needed for SCIM
185-
user, _, err := api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
204+
user, _, err = api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
186205
CreateUserRequest: codersdk.CreateUserRequest{
187206
Username: sUser.UserName,
188207
Email: email,

enterprise/coderd/scim_test.go

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,39 @@ func TestScim(t *testing.T) {
131131
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
132132
})
133133

134+
t.Run("Duplicate", func(t *testing.T) {
135+
t.Parallel()
136+
137+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
138+
defer cancel()
139+
140+
scimAPIKey := []byte("hi")
141+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
142+
SCIMAPIKey: scimAPIKey,
143+
LicenseOptions: &coderdenttest.LicenseOptions{
144+
AccountID: "coolin",
145+
Features: license.Features{
146+
codersdk.FeatureSCIM: 1,
147+
},
148+
},
149+
})
150+
151+
sUser := makeScimUser(t)
152+
for i := 0; i < 3; i++ {
153+
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
154+
require.NoError(t, err)
155+
_ = res.Body.Close()
156+
assert.Equal(t, http.StatusOK, res.StatusCode)
157+
}
158+
159+
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})
160+
require.NoError(t, err)
161+
require.Len(t, userRes.Users, 1)
162+
163+
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
164+
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
165+
})
166+
134167
t.Run("DomainStrips", func(t *testing.T) {
135168
t.Parallel()
136169

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