diff --git a/coderd/audit/request.go b/coderd/audit/request.go index 92b0a8a8accc2..6c862c6e11103 100644 --- a/coderd/audit/request.go +++ b/coderd/audit/request.go @@ -51,6 +51,8 @@ type Request[T Auditable] struct { Action database.AuditAction } +// UpdateOrganizationID can be used if the organization ID is not known +// at the initiation of an audit log request. func (r *Request[T]) UpdateOrganizationID(id uuid.UUID) { r.params.OrganizationID = id } diff --git a/coderd/coderdtest/coderdtest.go b/coderd/coderdtest/coderdtest.go index 643a1dd59d70c..9a1640e620d31 100644 --- a/coderd/coderdtest/coderdtest.go +++ b/coderd/coderdtest/coderdtest.go @@ -1064,25 +1064,6 @@ func (w WorkspaceAgentWaiter) Wait() []codersdk.WorkspaceResource { // CreateWorkspace creates a workspace for the user and template provided. // A random name is generated for it. // To customize the defaults, pass a mutator func. -// -// Deprecated: Use CreateWorkspace. -func CreateWorkspaceByOrganization(t testing.TB, client *codersdk.Client, organization uuid.UUID, templateID uuid.UUID, mutators ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace { - t.Helper() - req := codersdk.CreateWorkspaceRequest{ - TemplateID: templateID, - Name: RandomUsername(t), - AutostartSchedule: ptr.Ref("CRON_TZ=US/Central 30 9 * * 1-5"), - TTLMillis: ptr.Ref((8 * time.Hour).Milliseconds()), - AutomaticUpdates: codersdk.AutomaticUpdatesNever, - } - for _, mutator := range mutators { - mutator(&req) - } - workspace, err := client.CreateWorkspace(context.Background(), organization, codersdk.Me, req) - require.NoError(t, err) - return workspace -} - func CreateWorkspace(t testing.TB, client *codersdk.Client, templateID uuid.UUID, mutators ...func(*codersdk.CreateWorkspaceRequest)) codersdk.Workspace { t.Helper() req := codersdk.CreateWorkspaceRequest{ diff --git a/coderd/workspaces.go b/coderd/workspaces.go index 584651298f023..901e3723964bd 100644 --- a/coderd/workspaces.go +++ b/coderd/workspaces.go @@ -464,7 +464,7 @@ func createWorkspace( templateID := req.TemplateID if templateID == uuid.Nil { templateVersion, err := api.Database.GetTemplateVersionByID(ctx, req.TemplateVersionID) - if errors.Is(err, sql.ErrNoRows) { + if httpapi.Is404Error(err) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Template version %q doesn't exist.", templateID.String()), Validations: []codersdk.ValidationError{{ @@ -498,7 +498,7 @@ func createWorkspace( } template, err := api.Database.GetTemplateByID(ctx, templateID) - if errors.Is(err, sql.ErrNoRows) { + if httpapi.Is404Error(err) { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ Message: fmt.Sprintf("Template %q doesn't exist.", templateID.String()), Validations: []codersdk.ValidationError{{ @@ -521,8 +521,18 @@ func createWorkspace( }) return } + + // Update audit log's organization auditReq.UpdateOrganizationID(template.OrganizationID) + // Do this upfront to save work. If this fails, the rest of the work + // would be wasted. + if !api.Authorize(r, policy.ActionCreate, + rbac.ResourceWorkspace.InOrg(template.OrganizationID).WithOwner(owner.ID.String())) { + httpapi.ResourceNotFound(rw) + return + } + templateAccessControl := (*(api.AccessControlStore.Load())).GetTemplateAccessControl(template) if templateAccessControl.IsDeprecated() { httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{ @@ -578,7 +588,7 @@ func createWorkspace( // read other workspaces. Ideally we check the error on create and look for // a postgres conflict error. workspace, err := api.Database.GetWorkspaceByOwnerIDAndName(ctx, database.GetWorkspaceByOwnerIDAndNameParams{ - OwnerID: user.ID, + OwnerID: owner.ID, Name: req.Name, }) if err == nil { @@ -611,7 +621,7 @@ func createWorkspace( ID: uuid.New(), CreatedAt: now, UpdatedAt: now, - OwnerID: user.ID, + OwnerID: owner.ID, OrganizationID: template.OrganizationID, TemplateID: template.ID, Name: req.Name, @@ -679,8 +689,8 @@ func createWorkspace( ProvisionerJob: *provisionerJob, QueuePosition: 0, }, - user.Username, - user.AvatarURL, + owner.Username, + owner.AvatarURL, []database.WorkspaceResource{}, []database.WorkspaceResourceMetadatum{}, []database.WorkspaceAgent{}, @@ -702,8 +712,8 @@ func createWorkspace( workspace, apiBuild, template, - user.Username, - user.AvatarURL, + owner.Username, + owner.AvatarURL, api.Options.AllowWorkspaceRenames, ) if err != nil { diff --git a/codersdk/organizations.go b/codersdk/organizations.go index 8d8a9304d60fd..277d41cf9ae52 100644 --- a/codersdk/organizations.go +++ b/codersdk/organizations.go @@ -475,19 +475,8 @@ func (c *Client) TemplateByName(ctx context.Context, organizationID uuid.UUID, n // CreateWorkspace creates a new workspace for the template specified. // // Deprecated: Use CreateUserWorkspace instead. -func (c *Client) CreateWorkspace(ctx context.Context, organizationID uuid.UUID, user string, request CreateWorkspaceRequest) (Workspace, error) { - res, err := c.Request(ctx, http.MethodPost, fmt.Sprintf("/api/v2/organizations/%s/members/%s/workspaces", organizationID, user), request) - if err != nil { - return Workspace{}, err - } - defer res.Body.Close() - - if res.StatusCode != http.StatusCreated { - return Workspace{}, ReadBodyAsError(res) - } - - var workspace Workspace - return workspace, json.NewDecoder(res.Body).Decode(&workspace) +func (c *Client) CreateWorkspace(ctx context.Context, _ uuid.UUID, user string, request CreateWorkspaceRequest) (Workspace, error) { + return c.CreateUserWorkspace(ctx, user, request) } // CreateUserWorkspace creates a new workspace for the template specified. diff --git a/enterprise/coderd/workspaces_test.go b/enterprise/coderd/workspaces_test.go index 0f76da78c3da2..0b758e0491e1b 100644 --- a/enterprise/coderd/workspaces_test.go +++ b/enterprise/coderd/workspaces_test.go @@ -135,6 +135,58 @@ func TestCreateWorkspace(t *testing.T) { _, err = client1.CreateWorkspace(ctx, user.OrganizationID, user1.ID.String(), req) require.Error(t, err) }) + + t.Run("NoTemplateAccess", func(t *testing.T) { + t.Parallel() + ownerClient, owner := coderdenttest.New(t, &coderdenttest.Options{ + Options: &coderdtest.Options{ + IncludeProvisionerDaemon: true, + }, + LicenseOptions: &coderdenttest.LicenseOptions{ + Features: license.Features{ + codersdk.FeatureTemplateRBAC: 1, + }, + }, + }) + + templateAdmin, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleTemplateAdmin()) + user, _ := coderdtest.CreateAnotherUser(t, ownerClient, owner.OrganizationID, rbac.RoleMember()) + + version := coderdtest.CreateTemplateVersion(t, templateAdmin, owner.OrganizationID, nil) + coderdtest.AwaitTemplateVersionJobCompleted(t, templateAdmin, version.ID) + template := coderdtest.CreateTemplate(t, templateAdmin, owner.OrganizationID, version.ID) + + ctx, cancel := context.WithTimeout(context.Background(), testutil.WaitLong) + defer cancel() + + // Remove everyone access + err := templateAdmin.UpdateTemplateACL(ctx, template.ID, codersdk.UpdateTemplateACL{ + UserPerms: map[string]codersdk.TemplateRole{}, + GroupPerms: map[string]codersdk.TemplateRole{ + owner.OrganizationID.String(): codersdk.TemplateRoleDeleted, + }, + }) + require.NoError(t, err) + + // Test "everyone" access is revoked to the regular user + _, err = user.Template(ctx, template.ID) + require.Error(t, err) + var apiErr *codersdk.Error + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusNotFound, apiErr.StatusCode()) + + _, err = user.CreateUserWorkspace(ctx, codersdk.Me, codersdk.CreateWorkspaceRequest{ + TemplateID: template.ID, + Name: "random", + AutostartSchedule: ptr.Ref("CRON_TZ=US/Central 30 9 * * 1-5"), + TTLMillis: ptr.Ref((8 * time.Hour).Milliseconds()), + AutomaticUpdates: codersdk.AutomaticUpdatesNever, + }) + require.Error(t, err) + require.ErrorAs(t, err, &apiErr) + require.Equal(t, http.StatusBadRequest, apiErr.StatusCode()) + require.Contains(t, apiErr.Message, "doesn't exist") + }) } func TestCreateUserWorkspace(t *testing.T) { pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy