mirror of
https://github.com/coder/coder.git
synced 2026-10-16 14:48:56 +08:00
fix(coderd): reject agent requests from suspended owners (#28513)
## What `ExtractWorkspaceAgentAndLatestBuild` computed the workspace owner's RBAC subject via `UserRBACSubject`, but discarded the returned user status. As a result, workspace agent requests were still authorized even when the owner's account was suspended. This change captures `userStatus` and rejects the request with `401 Unauthorized` when the owner is not `Active`, returning a clear message directing the user to contact an admin to reactivate the account. ## Changes - `coderd/httpmw/workspaceagent.go`: stop discarding the owner status from `UserRBACSubject`; return `401` when the owner is not active. - `coderd/httpmw/workspaceagent_test.go`: add an `InactiveUser` test that suspends the owner and asserts a `401` with the expected message. ## Testing - New `TestWorkspaceAgent/InactiveUser` unit test covers the suspended-owner path. --- > This PR was created by Coder Agents on behalf of @hwang251.
This commit is contained in:
@@ -109,7 +109,7 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil
|
||||
return
|
||||
}
|
||||
|
||||
subject, _, err := UserRBACSubject(
|
||||
subject, userStatus, err := UserRBACSubject(
|
||||
ctx,
|
||||
opts.DB,
|
||||
row.WorkspaceTable.OwnerID,
|
||||
@@ -129,6 +129,12 @@ func ExtractWorkspaceAgentAndLatestBuild(opts ExtractWorkspaceAgentAndLatestBuil
|
||||
})
|
||||
return
|
||||
}
|
||||
if userStatus != database.UserStatusActive {
|
||||
httpapi.Write(ctx, rw, http.StatusUnauthorized, codersdk.Response{
|
||||
Message: fmt.Sprintf("User is not active (status = %q). Contact an admin to reactivate your account.", userStatus),
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
ctx = context.WithValue(ctx, workspaceAgentContextKey{}, row.WorkspaceAgent)
|
||||
ctx = context.WithValue(ctx, latestBuildContextKey{}, row.WorkspaceBuild)
|
||||
|
||||
@@ -1,7 +1,10 @@
|
||||
package httpmw_test
|
||||
|
||||
import (
|
||||
"context"
|
||||
"database/sql"
|
||||
"encoding/json"
|
||||
"io"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"testing"
|
||||
@@ -61,6 +64,38 @@ func TestWorkspaceAgent(t *testing.T) {
|
||||
require.Equal(t, http.StatusOK, res.StatusCode)
|
||||
})
|
||||
|
||||
t.Run("InactiveUser", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
authToken := uuid.New()
|
||||
req, rtr, workspace, _ := setup(t, db, authToken, httpmw.ExtractWorkspaceAgentAndLatestBuild(
|
||||
httpmw.ExtractWorkspaceAgentAndLatestBuildConfig{
|
||||
DB: db,
|
||||
Optional: false,
|
||||
}),
|
||||
)
|
||||
|
||||
_, err := db.UpdateUserStatus(context.Background(), database.UpdateUserStatusParams{
|
||||
ID: workspace.OwnerID,
|
||||
Status: database.UserStatusSuspended,
|
||||
UpdatedAt: dbtime.Now(),
|
||||
})
|
||||
require.NoError(t, err)
|
||||
|
||||
rw := httptest.NewRecorder()
|
||||
req.Header.Set(codersdk.SessionTokenHeader, authToken.String())
|
||||
rtr.ServeHTTP(rw, req)
|
||||
|
||||
res := rw.Result()
|
||||
defer res.Body.Close()
|
||||
require.Equal(t, http.StatusUnauthorized, res.StatusCode)
|
||||
body, err := io.ReadAll(res.Body)
|
||||
require.NoError(t, err)
|
||||
var response codersdk.Response
|
||||
require.NoError(t, json.Unmarshal(body, &response))
|
||||
require.Contains(t, response.Message, `User is not active (status = "suspended")`)
|
||||
})
|
||||
|
||||
t.Run("Latest", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
db, _ := dbtestutil.NewDB(t)
|
||||
|
||||
Reference in New Issue
Block a user