mirror of
https://github.com/coder/coder.git
synced 2026-06-02 20:48:20 +00:00
fix(cli): allow site admins to use coder create --org for any organization (#21528)
## Problem Site-wide admins (e.g., Owners) could not use `coder create --org <org>` to create workspaces in organizations they are not members of. The error was: ``` $ coder create my-workspace -t docker --org data-science error: organization "data-science" not found, are you sure you are a member of this organization? ``` This was inconsistent with the web UI, where Owners can create workspaces in any organization. ## Root Cause The CLI's `OrganizationContext.Selected()` function only checked the user's membership list, ignoring site-wide RBAC permissions that grant Owners access to all organizations. ## Solution Added a fallback in `OrganizationContext.Selected()` that fetches the org directly via the API when not found in the membership list. This works because the API endpoint applies RBAC filtering, allowing Owners to read any org. ## Impact This fixes `coder create --org` and all other CLI commands that use `OrganizationContext.Selected()` (29+ commands), including: - `coder templates push --org <any-org>` - `coder organizations members add --org <any-org>` - `coder provisioner list --org <any-org>` ## Testing Added `TestEnterpriseCreate/OwnerCanCreateInNonMemberOrg` which: - Creates an Owner user who is NOT a member of a second org - Verifies they can create a workspace there using `--org` - Properly fails without the code fix, passes with it --- *This PR was generated by [mux](https://mux.coder.com) but reviewed by a human.*
This commit is contained in:
+15
-4
@@ -884,16 +884,27 @@ func (o *OrganizationContext) Selected(inv *serpent.Invocation, client *codersdk
|
||||
index := slices.IndexFunc(orgs, func(org codersdk.Organization) bool {
|
||||
return org.Name == o.FlagSelect || org.ID.String() == o.FlagSelect
|
||||
})
|
||||
if index >= 0 {
|
||||
return orgs[index], nil
|
||||
}
|
||||
|
||||
if index < 0 {
|
||||
// Not in membership list - try direct fetch.
|
||||
// This allows site-wide admins (e.g., Owners) to use orgs they aren't
|
||||
// members of.
|
||||
org, err := client.OrganizationByName(inv.Context(), o.FlagSelect)
|
||||
if err != nil {
|
||||
var names []string
|
||||
for _, org := range orgs {
|
||||
names = append(names, org.Name)
|
||||
}
|
||||
return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization? "+
|
||||
"Valid options for '--org=' are [%s].", o.FlagSelect, strings.Join(names, ", "))
|
||||
var sdkErr *codersdk.Error
|
||||
if errors.As(err, &sdkErr) && sdkErr.StatusCode() == http.StatusNotFound {
|
||||
return codersdk.Organization{}, xerrors.Errorf("organization %q not found, are you sure you are a member of this organization? "+
|
||||
"Valid options for '--org=' are [%s].", o.FlagSelect, strings.Join(names, ", "))
|
||||
}
|
||||
return codersdk.Organization{}, xerrors.Errorf("get organization %q: %w", o.FlagSelect, err)
|
||||
}
|
||||
return orgs[index], nil
|
||||
return org, nil
|
||||
}
|
||||
|
||||
if len(orgs) == 1 {
|
||||
|
||||
@@ -192,6 +192,41 @@ func TestEnterpriseCreate(t *testing.T) {
|
||||
}
|
||||
})
|
||||
|
||||
// Site-wide admins (Owners) can create workspaces in organizations they
|
||||
// are not a member of by using the --org flag.
|
||||
t.Run("OwnerCanCreateInNonMemberOrg", func(t *testing.T) {
|
||||
t.Parallel()
|
||||
|
||||
const templateName = "ownertemplate"
|
||||
setup := setupMultipleOrganizations(t, setupArgs{
|
||||
secondTemplates: []string{templateName},
|
||||
})
|
||||
|
||||
// Create a new Owner user who is NOT a member of the second org.
|
||||
// The setup.owner created the second org and is auto-added as member,
|
||||
// so we need a different Owner to test the RBAC-only path.
|
||||
newOwner, _ := coderdtest.CreateAnotherUser(t, setup.owner, setup.firstResponse.OrganizationID, rbac.RoleOwner())
|
||||
|
||||
args := []string{
|
||||
"create",
|
||||
"owner-workspace",
|
||||
"-y",
|
||||
"--template", templateName,
|
||||
"--org", setup.second.Name,
|
||||
}
|
||||
inv, root := clitest.New(t, args...)
|
||||
clitest.SetupConfig(t, newOwner, root)
|
||||
_ = ptytest.New(t).Attach(inv)
|
||||
err := inv.Run()
|
||||
require.NoError(t, err)
|
||||
|
||||
ws, err := newOwner.WorkspaceByOwnerAndName(context.Background(), codersdk.Me, "owner-workspace", codersdk.WorkspaceOptions{})
|
||||
if assert.NoError(t, err, "expected workspace to be created") {
|
||||
assert.Equal(t, ws.TemplateName, templateName)
|
||||
assert.Equal(t, ws.OrganizationName, setup.second.Name, "workspace in second organization")
|
||||
}
|
||||
})
|
||||
|
||||
// If an organization is specified, but the template is not in that
|
||||
// organization, an error is thrown.
|
||||
t.Run("CreateIncorrectOrg", func(t *testing.T) {
|
||||
|
||||
Reference in New Issue
Block a user