Skip to content

Commit 943b04f

Browse files
jdomeracki-coderpawbanaf0ssel
authored
fix: check user user is active in aibridge auth (#26173) (#26264)
Cherry-pick backport to `release/2.34`. Co-authored-by: Paweł Banaszewski <pawel@coder.com> Co-authored-by: Garrett Delfosse <delfossegarrett@gmail.com>
1 parent 6293c89 commit 943b04f

2 files changed

Lines changed: 54 additions & 6 deletions

File tree

coderd/aibridgedserver/aibridgedserver.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ var (
4343
ErrExpired = xerrors.New("expired")
4444
ErrUnknownUser = xerrors.New("unknown user")
4545
ErrDeletedUser = xerrors.New("deleted user")
46+
ErrInactiveUser = xerrors.New("inactive user")
4647
ErrSystemUser = xerrors.New("system user")
4748
ErrAmbiguousAuth = xerrors.New("both key and key_id set; exactly one required")
4849

@@ -623,10 +624,13 @@ func (s *Server) IsAuthorized(ctx context.Context, in *proto.IsAuthorizedRequest
623624
return nil, ErrUnknownUser
624625
}
625626

626-
// User is not deleted or a system user.
627+
// User is active, not deleted, and not a system user.
627628
if user.Deleted {
628629
return nil, ErrDeletedUser
629630
}
631+
if user.Status != database.UserStatusActive {
632+
return nil, ErrInactiveUser
633+
}
630634
if user.IsSystem {
631635
return nil, ErrSystemUser
632636
}

coderd/aibridgedserver/aibridgedserver_test.go

Lines changed: 49 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -102,16 +102,36 @@ func TestAuthorization(t *testing.T) {
102102
name: "deleted user",
103103
expectedErr: aibridgedserver.ErrDeletedUser,
104104
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
105+
user.Deleted = true
105106
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
106-
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{ID: user.ID, Deleted: true}, nil)
107+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
108+
},
109+
},
110+
{
111+
name: "suspended user",
112+
expectedErr: aibridgedserver.ErrInactiveUser,
113+
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
114+
user.Status = database.UserStatusSuspended
115+
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
116+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
117+
},
118+
},
119+
{
120+
name: "dormant user",
121+
expectedErr: aibridgedserver.ErrInactiveUser,
122+
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
123+
user.Status = database.UserStatusDormant
124+
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
125+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
107126
},
108127
},
109128
{
110129
name: "system user",
111130
expectedErr: aibridgedserver.ErrSystemUser,
112131
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
132+
user.IsSystem = true
113133
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
114-
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{ID: user.ID, IsSystem: true}, nil)
134+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
115135
},
116136
},
117137
{
@@ -201,7 +221,7 @@ func TestAuthorization(t *testing.T) {
201221

202222
// When IsAuthorizedRequest carries KeyId instead of Key, the server skips
203223
// the secret check and validates only that the key exists, is unexpired, and
204-
// belongs to a non-deleted non-system user. This is the path used by
224+
// belongs to an active, non-deleted, non-system user. This is the path used by
205225
// in-process delegated callers (e.g., chatd) that hold only the key ID.
206226
func TestAuthorization_Delegated(t *testing.T) {
207227
t.Parallel()
@@ -260,8 +280,31 @@ func TestAuthorization_Delegated(t *testing.T) {
260280
name: "deleted user",
261281
expectedErr: aibridgedserver.ErrDeletedUser,
262282
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
283+
user.Deleted = true
263284
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
264-
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{ID: user.ID, Deleted: true}, nil)
285+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
286+
},
287+
},
288+
{
289+
// The delegated path must reject inactive users; transport
290+
// trust does not override account suspension.
291+
name: "suspended user",
292+
expectedErr: aibridgedserver.ErrInactiveUser,
293+
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
294+
user.Status = database.UserStatusSuspended
295+
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
296+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
297+
},
298+
},
299+
{
300+
// Dormant users are inactive unless they are explicitly
301+
// reactivated through the HTTP middleware path.
302+
name: "dormant user",
303+
expectedErr: aibridgedserver.ErrInactiveUser,
304+
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
305+
user.Status = database.UserStatusDormant
306+
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
307+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
265308
},
266309
},
267310
{
@@ -270,8 +313,9 @@ func TestAuthorization_Delegated(t *testing.T) {
270313
name: "system user",
271314
expectedErr: aibridgedserver.ErrSystemUser,
272315
mocksFn: func(db *dbmock.MockStore, apiKey database.APIKey, user database.User) {
316+
user.IsSystem = true
273317
db.EXPECT().GetAPIKeyByID(gomock.Any(), apiKey.ID).Times(1).Return(apiKey, nil)
274-
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(database.User{ID: user.ID, IsSystem: true}, nil)
318+
db.EXPECT().GetUserByID(gomock.Any(), user.ID).Times(1).Return(user, nil)
275319
},
276320
},
277321
}

0 commit comments

Comments
 (0)