diff --git a/coderd/parameters.go b/coderd/parameters.go index d8551b2031f7a..48cccc27e6727 100644 --- a/coderd/parameters.go +++ b/coderd/parameters.go @@ -58,11 +58,25 @@ func (api *API) templateVersionDynamicParametersEvaluate(rw http.ResponseWriter, // @Router /templateversions/{templateversion}/dynamic-parameters [get] func (api *API) templateVersionDynamicParametersWebsocket(rw http.ResponseWriter, r *http.Request) { apikey := httpmw.APIKey(r) + userID := apikey.UserID + + qUserID := r.URL.Query().Get("user_id") + if qUserID != "" && qUserID != codersdk.Me { + uid, err := uuid.Parse(qUserID) + if err != nil { + httpapi.Write(r.Context(), rw, http.StatusBadRequest, codersdk.Response{ + Message: "Invalid user_id query parameter", + Detail: err.Error(), + }) + return + } + userID = uid + } api.templateVersionDynamicParameters(true, codersdk.DynamicParametersRequest{ ID: -1, Inputs: map[string]string{}, - OwnerID: apikey.UserID, + OwnerID: userID, })(rw, r) } diff --git a/coderd/parameters_test.go b/coderd/parameters_test.go index 640dc3ad22e55..3c792c2ce9a7a 100644 --- a/coderd/parameters_test.go +++ b/coderd/parameters_test.go @@ -56,7 +56,7 @@ func TestDynamicParametersOwnerSSHPublicKey(t *testing.T) { _ = coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID) ctx := testutil.Context(t, testutil.WaitShort) - stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, version.ID) + stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, codersdk.Me, version.ID) require.NoError(t, err) defer stream.Close(websocket.StatusGoingAway) @@ -387,7 +387,7 @@ func setupDynamicParamsTest(t *testing.T, args setupDynamicParamsTestParams) dyn require.NoError(t, err) ctx := testutil.Context(t, testutil.WaitShort) - stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, version.ID) + stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, codersdk.Me, version.ID) if args.expectWebsocketError { require.Errorf(t, err, "expected error forming websocket") } else { diff --git a/codersdk/parameters.go b/codersdk/parameters.go index 035537d34259e..bdf48f2c6e8fa 100644 --- a/codersdk/parameters.go +++ b/codersdk/parameters.go @@ -5,6 +5,7 @@ import ( "fmt" "github.com/google/uuid" + "golang.org/x/xerrors" "github.com/coder/coder/v2/codersdk/wsjson" "github.com/coder/websocket" @@ -125,8 +126,17 @@ type DynamicParametersResponse struct { // TODO: Workspace tags } -func (c *Client) TemplateVersionDynamicParameters(ctx context.Context, version uuid.UUID) (*wsjson.Stream[DynamicParametersResponse, DynamicParametersRequest], error) { - conn, err := c.Dial(ctx, fmt.Sprintf("/api/v2/templateversions/%s/dynamic-parameters", version), nil) +func (c *Client) TemplateVersionDynamicParameters(ctx context.Context, userID string, version uuid.UUID) (*wsjson.Stream[DynamicParametersResponse, DynamicParametersRequest], error) { + endpoint := fmt.Sprintf("/api/v2/templateversions/%s/dynamic-parameters", version) + if userID != Me { + uid, err := uuid.Parse(userID) + if err != nil { + return nil, xerrors.Errorf("invalid user ID: %w", err) + } + endpoint += fmt.Sprintf("?user_id=%s", uid.String()) + } + + conn, err := c.Dial(ctx, endpoint, nil) if err != nil { return nil, err } diff --git a/enterprise/coderd/parameters_test.go b/enterprise/coderd/parameters_test.go index 5fc0eaa4aa369..93f5057206527 100644 --- a/enterprise/coderd/parameters_test.go +++ b/enterprise/coderd/parameters_test.go @@ -32,6 +32,7 @@ func TestDynamicParametersOwnerGroups(t *testing.T) { }, ) templateAdmin, templateAdminUser := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + _, noGroupUser := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID) // Create the group to be asserted group := coderdtest.CreateGroup(t, ownerClient, owner.OrganizationID, "bloob", templateAdminUser) @@ -57,7 +58,24 @@ func TestDynamicParametersOwnerGroups(t *testing.T) { _ = coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID) ctx := testutil.Context(t, testutil.WaitShort) - stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, version.ID) + + // First check with a no group admin user, that they do not see the extra group + // Use the admin client, as the user might not have access to the template. + // Also checking that the admin can see the form for the other user. + noGroupStream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, noGroupUser.ID.String(), version.ID) + require.NoError(t, err) + defer noGroupStream.Close(websocket.StatusGoingAway) + noGroupPreviews := noGroupStream.Chan() + noGroupPreview := testutil.RequireReceive(ctx, t, noGroupPreviews) + require.Equal(t, -1, noGroupPreview.ID) + require.Empty(t, noGroupPreview.Diagnostics) + require.Equal(t, "group", noGroupPreview.Parameters[0].Name) + require.Equal(t, database.EveryoneGroup, noGroupPreview.Parameters[0].Value.Value) + require.Equal(t, 1, len(noGroupPreview.Parameters[0].Options)) // Only 1 group + noGroupStream.Close(websocket.StatusGoingAway) + + // Now try with a user with more than 1 group + stream, err := templateAdmin.TemplateVersionDynamicParameters(ctx, codersdk.Me, version.ID) require.NoError(t, err) defer stream.Close(websocket.StatusGoingAway) diff --git a/site/src/api/api.ts b/site/src/api/api.ts index 28807bd547c2a..5b7cde65fb2ce 100644 --- a/site/src/api/api.ts +++ b/site/src/api/api.ts @@ -1017,6 +1017,7 @@ class ApiMethods { templateVersionDynamicParameters = ( versionId: string, + userId: string, { onMessage, onError, @@ -1029,6 +1030,7 @@ class ApiMethods { ): WebSocket => { const socket = createWebSocket( `/api/v2/templateversions/${versionId}/dynamic-parameters`, + new URLSearchParams({ user_id: userId }), ); socket.addEventListener("message", (event) => diff --git a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx index cf0e80d592cd6..070576a5e9a99 100644 --- a/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx +++ b/site/src/pages/CreateWorkspacePage/CreateWorkspacePageExperimental.tsx @@ -148,31 +148,35 @@ const CreateWorkspacePageExperimental: FC = () => { useEffect(() => { if (!realizedVersionId) return; - const socket = API.templateVersionDynamicParameters(realizedVersionId, { - onMessage, - onError: (error) => { - if (ws.current === socket) { - setWsError(error); - } - }, - onClose: () => { - if (ws.current === socket) { - setWsError( - new DetailedError( - "Websocket connection for dynamic parameters unexpectedly closed.", - "Refresh the page to reset the form.", - ), - ); - } + const socket = API.templateVersionDynamicParameters( + realizedVersionId, + defaultOwner.id, + { + onMessage, + onError: (error) => { + if (ws.current === socket) { + setWsError(error); + } + }, + onClose: () => { + if (ws.current === socket) { + setWsError( + new DetailedError( + "Websocket connection for dynamic parameters unexpectedly closed.", + "Refresh the page to reset the form.", + ), + ); + } + }, }, - }); + ); ws.current = socket; return () => { socket.close(); }; - }, [realizedVersionId, onMessage]); + }, [realizedVersionId, onMessage, defaultOwner.id]); const organizationId = templateQuery.data?.organization_id; diff --git a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx index 755291ec28629..68340ddad5e05 100644 --- a/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx +++ b/site/src/pages/WorkspaceSettingsPage/WorkspaceParametersPage/WorkspaceParametersPageExperimental.tsx @@ -111,6 +111,7 @@ const WorkspaceParametersPageExperimental: FC = () => { const socket = API.templateVersionDynamicParameters( templateVersionId ?? workspace.latest_build.template_version_id, + workspace.owner_id, { onMessage, onError: (error) => { @@ -140,6 +141,7 @@ const WorkspaceParametersPageExperimental: FC = () => { templateVersionId, workspace.latest_build.template_version_id, onMessage, + workspace.owner_id, ]); const updateParameters = useMutation({