mirror of
https://github.com/coder/coder.git
synced 2026-06-05 05:58:20 +00:00
909acbc833
Adds a `sharing add` command for sharing Workspaces with other users and groups. The command allows sharing with multiple users, and groups within one command as well as specifying the role (`use`, or `admin`) defaulting to `use` if none is specified. In the current implementation when the command completes we show the user the current state of the workspace ACL. ``` $ coder sharing add apricot-catfish-86 --user=member:admin --group=contractors:use USER GROUP ROLE member - admin member contractors use ``` If a user is a part of multiple groups, or the workspace has been individually shared with them they will show up multiple times. Although this is a bit confusing at first glance it's important to be able to tell what the maximum role a user may have, and via what ACL they have it. --- One piece of UX to consider is that in order to be able to share a Workspace with a user they must have a role that can read that user. In the tests we give the user the `ScopedRoleOrgAuditor` role. Closes [coder/internal#859](https://github.com/coder/internal/issues/859)
171 lines
5.9 KiB
Go
171 lines
5.9 KiB
Go
package cli_test
|
|
|
|
import (
|
|
"bytes"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
|
|
"github.com/coder/coder/v2/cli/clitest"
|
|
"github.com/coder/coder/v2/coderd/coderdtest"
|
|
"github.com/coder/coder/v2/coderd/database"
|
|
"github.com/coder/coder/v2/coderd/database/dbfake"
|
|
"github.com/coder/coder/v2/coderd/rbac"
|
|
"github.com/coder/coder/v2/codersdk"
|
|
"github.com/coder/coder/v2/testutil"
|
|
)
|
|
|
|
func TestSharingShare(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
dv := coderdtest.DeploymentValues(t)
|
|
dv.Experiments = []string{string(codersdk.ExperimentWorkspaceSharing)}
|
|
|
|
t.Run("ShareWithUsers_Simple", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
|
DeploymentValues: dv,
|
|
})
|
|
orgOwner = coderdtest.CreateFirstUser(t, client)
|
|
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
|
|
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
|
OwnerID: workspaceOwner.ID,
|
|
OrganizationID: orgOwner.OrganizationID,
|
|
}).Do().Workspace
|
|
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
|
)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
inv, root := clitest.New(t, "sharing", "add", workspace.Name, "--org", orgOwner.OrganizationID.String(), "--user", toShareWithUser.Username)
|
|
clitest.SetupConfig(t, workspaceOwnerClient, root)
|
|
|
|
out := bytes.NewBuffer(nil)
|
|
inv.Stdout = out
|
|
err := inv.WithContext(ctx).Run()
|
|
require.NoError(t, err)
|
|
|
|
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
|
MinimalUser: codersdk.MinimalUser{
|
|
ID: toShareWithUser.ID,
|
|
Username: toShareWithUser.Username,
|
|
AvatarURL: toShareWithUser.AvatarURL,
|
|
},
|
|
Role: codersdk.WorkspaceRole("use"),
|
|
})
|
|
|
|
assert.Contains(t, out.String(), toShareWithUser.Username)
|
|
assert.Contains(t, out.String(), codersdk.WorkspaceRoleUse)
|
|
})
|
|
|
|
t.Run("ShareWithUsers_Multiple", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
|
DeploymentValues: dv,
|
|
})
|
|
orgOwner = coderdtest.CreateFirstUser(t, client)
|
|
|
|
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
|
|
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
|
OwnerID: workspaceOwner.ID,
|
|
OrganizationID: orgOwner.OrganizationID,
|
|
}).Do().Workspace
|
|
|
|
_, toShareWithUser1 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
|
_, toShareWithUser2 = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
|
)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
inv, root := clitest.New(t,
|
|
"sharing",
|
|
"add", workspace.Name, "--org", orgOwner.OrganizationID.String(),
|
|
fmt.Sprintf("--user=%s,%s", toShareWithUser1.Username, toShareWithUser2.Username),
|
|
)
|
|
clitest.SetupConfig(t, workspaceOwnerClient, root)
|
|
|
|
out := bytes.NewBuffer(nil)
|
|
inv.Stdout = out
|
|
err := inv.WithContext(ctx).Run()
|
|
require.NoError(t, err)
|
|
|
|
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
|
MinimalUser: codersdk.MinimalUser{
|
|
ID: toShareWithUser1.ID,
|
|
Username: toShareWithUser1.Username,
|
|
AvatarURL: toShareWithUser1.AvatarURL,
|
|
},
|
|
Role: codersdk.WorkspaceRoleUse,
|
|
})
|
|
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
|
MinimalUser: codersdk.MinimalUser{
|
|
ID: toShareWithUser2.ID,
|
|
Username: toShareWithUser2.Username,
|
|
AvatarURL: toShareWithUser2.AvatarURL,
|
|
},
|
|
Role: codersdk.WorkspaceRoleUse,
|
|
})
|
|
|
|
assert.Contains(t, out.String(), toShareWithUser1.Username)
|
|
assert.Contains(t, out.String(), toShareWithUser2.Username)
|
|
})
|
|
|
|
t.Run("ShareWithUsers_Roles", func(t *testing.T) {
|
|
t.Parallel()
|
|
|
|
var (
|
|
client, db = coderdtest.NewWithDatabase(t, &coderdtest.Options{
|
|
DeploymentValues: dv,
|
|
})
|
|
orgOwner = coderdtest.CreateFirstUser(t, client)
|
|
workspaceOwnerClient, workspaceOwner = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID, rbac.ScopedRoleOrgAuditor(orgOwner.OrganizationID))
|
|
workspace = dbfake.WorkspaceBuild(t, db, database.WorkspaceTable{
|
|
OwnerID: workspaceOwner.ID,
|
|
OrganizationID: orgOwner.OrganizationID,
|
|
}).Do().Workspace
|
|
_, toShareWithUser = coderdtest.CreateAnotherUser(t, client, orgOwner.OrganizationID)
|
|
)
|
|
|
|
ctx := testutil.Context(t, testutil.WaitMedium)
|
|
inv, root := clitest.New(t, "sharing", "add", workspace.Name,
|
|
"--org", orgOwner.OrganizationID.String(),
|
|
"--user", fmt.Sprintf("%s:admin", toShareWithUser.Username),
|
|
)
|
|
clitest.SetupConfig(t, workspaceOwnerClient, root)
|
|
|
|
out := bytes.NewBuffer(nil)
|
|
inv.Stdout = out
|
|
err := inv.WithContext(ctx).Run()
|
|
require.NoError(t, err)
|
|
|
|
acl, err := workspaceOwnerClient.WorkspaceACL(inv.Context(), workspace.ID)
|
|
require.NoError(t, err)
|
|
assert.Contains(t, acl.Users, codersdk.WorkspaceUser{
|
|
MinimalUser: codersdk.MinimalUser{
|
|
ID: toShareWithUser.ID,
|
|
Username: toShareWithUser.Username,
|
|
AvatarURL: toShareWithUser.AvatarURL,
|
|
},
|
|
Role: codersdk.WorkspaceRoleAdmin,
|
|
})
|
|
|
|
found := false
|
|
for _, line := range strings.Split(out.String(), "\n") {
|
|
if strings.Contains(line, toShareWithUser.Username) && strings.Contains(line, string(codersdk.WorkspaceRoleAdmin)) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
assert.True(t, found, fmt.Sprintf("expected to find the username %s and role %s in the command: %s", toShareWithUser.Username, codersdk.WorkspaceRoleAdmin, out.String()))
|
|
})
|
|
}
|