Skip to content

Commit 0c7ff4f

Browse files
authored
fix(enterprise): ensure SCIM create user can unsuspend (#8916)
1 parent 8f7b6a2 commit 0c7ff4f

File tree

2 files changed

+79
-11
lines changed

2 files changed

+79
-11
lines changed

enterprise/coderd/scim.go

+22-7
Original file line numberDiff line numberDiff line change
@@ -155,7 +155,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
155155
}
156156

157157
//nolint:gocritic
158-
user, err := api.Database.GetUserByEmailOrUsername(dbauthz.AsSystemRestricted(ctx), database.GetUserByEmailOrUsernameParams{
158+
dbUser, err := api.Database.GetUserByEmailOrUsername(dbauthz.AsSystemRestricted(ctx), database.GetUserByEmailOrUsernameParams{
159159
Email: email,
160160
Username: sUser.UserName,
161161
})
@@ -164,8 +164,22 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
164164
return
165165
}
166166
if err == nil {
167-
sUser.ID = user.ID.String()
168-
sUser.UserName = user.Username
167+
sUser.ID = dbUser.ID.String()
168+
sUser.UserName = dbUser.Username
169+
170+
if sUser.Active && dbUser.Status == database.UserStatusSuspended {
171+
//nolint:gocritic
172+
_, err = api.Database.UpdateUserStatus(dbauthz.AsSystemRestricted(r.Context()), database.UpdateUserStatusParams{
173+
ID: dbUser.ID,
174+
// The user will get transitioned to Active after logging in.
175+
Status: database.UserStatusDormant,
176+
UpdatedAt: database.Now(),
177+
})
178+
if err != nil {
179+
_ = handlerutil.WriteError(rw, err)
180+
return
181+
}
182+
}
169183

170184
httpapi.Write(ctx, rw, http.StatusOK, sUser)
171185
return
@@ -201,7 +215,7 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
201215
}
202216

203217
//nolint:gocritic // needed for SCIM
204-
user, _, err = api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
218+
dbUser, _, err = api.AGPL.CreateUser(dbauthz.AsSystemRestricted(ctx), api.Database, agpl.CreateUserRequest{
205219
CreateUserRequest: codersdk.CreateUserRequest{
206220
Username: sUser.UserName,
207221
Email: email,
@@ -214,8 +228,8 @@ func (api *API) scimPostUser(rw http.ResponseWriter, r *http.Request) {
214228
return
215229
}
216230

217-
sUser.ID = user.ID.String()
218-
sUser.UserName = user.Username
231+
sUser.ID = dbUser.ID.String()
232+
sUser.UserName = dbUser.Username
219233

220234
httpapi.Write(ctx, rw, http.StatusOK, sUser)
221235
}
@@ -263,7 +277,8 @@ func (api *API) scimPatchUser(rw http.ResponseWriter, r *http.Request) {
263277

264278
var status database.UserStatus
265279
if sUser.Active {
266-
status = database.UserStatusActive
280+
// The user will get transitioned to Active after logging in.
281+
status = database.UserStatusDormant
267282
} else {
268283
status = database.UserStatusSuspended
269284
}

enterprise/coderd/scim_test.go

+57-4
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"context"
55
"encoding/json"
66
"fmt"
7+
"io"
78
"net/http"
89
"testing"
910

@@ -164,6 +165,54 @@ func TestScim(t *testing.T) {
164165
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
165166
})
166167

168+
t.Run("Unsuspend", func(t *testing.T) {
169+
t.Parallel()
170+
171+
ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong)
172+
defer cancel()
173+
174+
scimAPIKey := []byte("hi")
175+
client, _ := coderdenttest.New(t, &coderdenttest.Options{
176+
SCIMAPIKey: scimAPIKey,
177+
LicenseOptions: &coderdenttest.LicenseOptions{
178+
AccountID: "coolin",
179+
Features: license.Features{
180+
codersdk.FeatureSCIM: 1,
181+
},
182+
},
183+
})
184+
185+
sUser := makeScimUser(t)
186+
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
187+
require.NoError(t, err)
188+
defer res.Body.Close()
189+
assert.Equal(t, http.StatusOK, res.StatusCode)
190+
err = json.NewDecoder(res.Body).Decode(&sUser)
191+
require.NoError(t, err)
192+
193+
sUser.Active = false
194+
res, err = client.Request(ctx, "PATCH", "/scim/v2/Users/"+sUser.ID, sUser, setScimAuth(scimAPIKey))
195+
require.NoError(t, err)
196+
_, _ = io.Copy(io.Discard, res.Body)
197+
_ = res.Body.Close()
198+
assert.Equal(t, http.StatusOK, res.StatusCode)
199+
200+
sUser.Active = true
201+
res, err = client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
202+
require.NoError(t, err)
203+
_, _ = io.Copy(io.Discard, res.Body)
204+
_ = res.Body.Close()
205+
assert.Equal(t, http.StatusOK, res.StatusCode)
206+
207+
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})
208+
require.NoError(t, err)
209+
require.Len(t, userRes.Users, 1)
210+
211+
assert.Equal(t, sUser.Emails[0].Value, userRes.Users[0].Email)
212+
assert.Equal(t, sUser.UserName, userRes.Users[0].Username)
213+
assert.Equal(t, codersdk.UserStatusDormant, userRes.Users[0].Status)
214+
})
215+
167216
t.Run("DomainStrips", func(t *testing.T) {
168217
t.Parallel()
169218

@@ -185,7 +234,8 @@ func TestScim(t *testing.T) {
185234
sUser.UserName = sUser.UserName + "@coder.com"
186235
res, err := client.Request(ctx, "POST", "/scim/v2/Users", sUser, setScimAuth(scimAPIKey))
187236
require.NoError(t, err)
188-
defer res.Body.Close()
237+
_, _ = io.Copy(io.Discard, res.Body)
238+
_ = res.Body.Close()
189239
assert.Equal(t, http.StatusOK, res.StatusCode)
190240

191241
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})
@@ -220,7 +270,8 @@ func TestScim(t *testing.T) {
220270

221271
res, err := client.Request(ctx, "PATCH", "/scim/v2/Users/bob", struct{}{})
222272
require.NoError(t, err)
223-
defer res.Body.Close()
273+
_, _ = io.Copy(io.Discard, res.Body)
274+
_ = res.Body.Close()
224275
assert.Equal(t, http.StatusNotFound, res.StatusCode)
225276
})
226277

@@ -242,7 +293,8 @@ func TestScim(t *testing.T) {
242293

243294
res, err := client.Request(ctx, "PATCH", "/scim/v2/Users/bob", struct{}{})
244295
require.NoError(t, err)
245-
defer res.Body.Close()
296+
_, _ = io.Copy(io.Discard, res.Body)
297+
_ = res.Body.Close()
246298
assert.Equal(t, http.StatusInternalServerError, res.StatusCode)
247299
})
248300

@@ -276,7 +328,8 @@ func TestScim(t *testing.T) {
276328

277329
res, err = client.Request(ctx, "PATCH", "/scim/v2/Users/"+sUser.ID, sUser, setScimAuth(scimAPIKey))
278330
require.NoError(t, err)
279-
defer res.Body.Close()
331+
_, _ = io.Copy(io.Discard, res.Body)
332+
_ = res.Body.Close()
280333
assert.Equal(t, http.StatusOK, res.StatusCode)
281334

282335
userRes, err := client.Users(ctx, codersdk.UsersRequest{Search: sUser.Emails[0].Value})

0 commit comments

Comments
 (0)