fix(coderd): enforce required external auth on task create (#26718)

Tasks created through the API now enforce required external auth:
`tasksCreate` rejects an owner who is missing a required (non-optional)
provider with a 403 before generating a task name or inserting any rows,
matching the gate `createWorkspace` already applies to workspaces. Adds
`TestCreateTaskExternalAuth` covering the required and optional-provider
cases.

Fixes PLAT-298.

_Coder Agents generated._
This commit is contained in:
dylanhuff-at-coder
2026-07-09 13:59:49 -07:00
committed by GitHub
parent 37558fcdc9
commit 5fed583a46
3 changed files with 351 additions and 59 deletions
+46 -18
View File
@@ -121,24 +121,10 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
}
}
// Generate task name and display name if either is not provided
if taskName == "" || taskDisplayName == "" {
generatedTaskName := taskname.Generate(ctx, api.Logger, req.Input)
if taskName == "" {
taskName = generatedTaskName.Name
}
if taskDisplayName == "" {
taskDisplayName = generatedTaskName.DisplayName
}
}
createReq := codersdk.CreateWorkspaceRequest{
Name: taskName,
TemplateVersionID: req.TemplateVersionID,
TemplateVersionPresetID: req.TemplateVersionPresetID,
}
// Resolve the workspace owner before generating a task name so required
// external auth can be enforced up front. createWorkspace performs the same
// validation, but checking here keeps the Tasks API aligned with the gates
// the UI presents and avoids generating a name for a task that is rejected.
var owner workspaceOwner
if mems.User != nil {
// This user fetch is an optimization path for the most common case of creating a
@@ -177,6 +163,48 @@ func (api *API) tasksCreate(rw http.ResponseWriter, r *http.Request) {
taskResourceInfo.WorkspaceOwner = owner.Username
}
// Authorize workspace creation before the external auth preflight below.
// createWorkspace re-checks these gates as the authoritative defense, but
// requireWorkspaceOwnerExternalAuth validates (and may refresh or clear) the
// owner's external auth tokens under a system-restricted context. Running it
// before proving the caller may create a workspace for this owner using this
// template would let an unauthorized caller trigger token refresh side
// effects and probe another user's auth state. Mirror the ordering in
// createWorkspace so the side-effectful preflight only runs once the caller
// is authorized.
if _, err := api.preflightWorkspaceCreate(ctx, owner.ID, codersdk.CreateWorkspaceRequest{
TemplateVersionID: req.TemplateVersionID,
}); err != nil {
httperror.WriteResponseError(ctx, rw, err)
return
}
// Required external auth is otherwise only enforced once createWorkspace
// runs. Validate it here so the Tasks API rejects an owner who is missing a
// required provider before any task name generation or row insertion.
if err := api.requireWorkspaceOwnerExternalAuth(ctx, templateVersion, owner.ID); err != nil {
httperror.WriteResponseError(ctx, rw, err)
return
}
// Generate task name and display name if either is not provided
if taskName == "" || taskDisplayName == "" {
generatedTaskName := taskname.Generate(ctx, api.Logger, req.Input)
if taskName == "" {
taskName = generatedTaskName.Name
}
if taskDisplayName == "" {
taskDisplayName = generatedTaskName.DisplayName
}
}
createReq := codersdk.CreateWorkspaceRequest{
Name: taskName,
TemplateVersionID: req.TemplateVersionID,
TemplateVersionPresetID: req.TemplateVersionPresetID,
}
// Track insert from preCreateInTX.
var dbTaskTable database.TaskTable