Skip to content

Commit 103967e

Browse files
authored
feat: add sharing info to /workspaces endpoint (#21049)
closes: coder/internal#858 Similar to #19375, this one uses system permissions for fetching actual user and group data. Modifies the `workspaces_expanded` view to fetch the required data; this way it's made available to all code paths that make use of it. Also fixes a bug in a test helper function that can result in `null` being saved to the DB for `user_acl` or `group_acl` and break tests; a defensive check constraint that prevents this is worth a PR, e.g: `ALTER TABLE workspaces ADD CONSTRAINT group_acl_is_object CHECK (jsonb_typeof(group_acl) = 'object');` Also adds missing `OwnerName` in `ConvertWorkspaceRows`.
1 parent 7ecfd1a commit 103967e

File tree

22 files changed

+838
-92
lines changed

22 files changed

+838
-92
lines changed

coderd/aitasks.go

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -471,15 +471,25 @@ func (api *API) convertTasks(ctx context.Context, requesterID uuid.UUID, dbTasks
471471
return nil, xerrors.Errorf("fetch workspaces: %w", err)
472472
}
473473

474-
workspaces := database.ConvertWorkspaceRows(workspaceRows)
474+
workspaces, err := database.ConvertWorkspaceRows(workspaceRows)
475+
if err != nil {
476+
return nil, xerrors.Errorf("convert workspace rows: %w", err)
477+
}
475478

476479
// Gather associated data and convert to API workspaces.
477480
data, err := api.workspaceData(ctx, workspaces)
478481
if err != nil {
479482
return nil, xerrors.Errorf("fetch workspace data: %w", err)
480483
}
481484

482-
apiWorkspaces, err := convertWorkspaces(requesterID, workspaces, data)
485+
apiWorkspaces, err := convertWorkspaces(
486+
ctx,
487+
api.Experiments,
488+
api.Logger,
489+
requesterID,
490+
workspaces,
491+
data,
492+
)
483493
if err != nil {
484494
return nil, xerrors.Errorf("convert workspaces: %w", err)
485495
}
@@ -553,6 +563,9 @@ func (api *API) taskGet(rw http.ResponseWriter, r *http.Request) {
553563
}
554564

555565
ws, err := convertWorkspace(
566+
ctx,
567+
api.Experiments,
568+
api.Logger,
556569
apiKey.UserID,
557570
workspace,
558571
data.builds[0],

coderd/apidoc/docs.go

Lines changed: 50 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/apidoc/swagger.json

Lines changed: 44 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

coderd/database/dbgen/dbgen.go

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -440,10 +440,18 @@ func Workspace(t testing.TB, db database.Store, orig database.WorkspaceTable) da
440440
workspace.DormantAt = orig.DormantAt
441441
}
442442
if len(orig.UserACL) > 0 || len(orig.GroupACL) > 0 {
443+
userACL := orig.UserACL
444+
if userACL == nil {
445+
userACL = database.WorkspaceACL{}
446+
}
447+
groupACL := orig.GroupACL
448+
if groupACL == nil {
449+
groupACL = database.WorkspaceACL{}
450+
}
443451
err = db.UpdateWorkspaceACLByID(genCtx, database.UpdateWorkspaceACLByIDParams{
444452
ID: workspace.ID,
445-
UserACL: orig.UserACL,
446-
GroupACL: orig.GroupACL,
453+
UserACL: userACL,
454+
GroupACL: groupACL,
447455
})
448456
require.NoError(t, err, "set workspace ACL")
449457
workspace.UserACL = orig.UserACL

coderd/database/dump.sql

Lines changed: 7 additions & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 41 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,41 @@
1+
DROP VIEW workspaces_expanded;
2+
3+
-- Revert to passing through raw user_acl and group_acl columns.
4+
CREATE VIEW workspaces_expanded AS
5+
SELECT workspaces.id,
6+
workspaces.created_at,
7+
workspaces.updated_at,
8+
workspaces.owner_id,
9+
workspaces.organization_id,
10+
workspaces.template_id,
11+
workspaces.deleted,
12+
workspaces.name,
13+
workspaces.autostart_schedule,
14+
workspaces.ttl,
15+
workspaces.last_used_at,
16+
workspaces.dormant_at,
17+
workspaces.deleting_at,
18+
workspaces.automatic_updates,
19+
workspaces.favorite,
20+
workspaces.next_start_at,
21+
workspaces.group_acl,
22+
workspaces.user_acl,
23+
visible_users.avatar_url AS owner_avatar_url,
24+
visible_users.username AS owner_username,
25+
visible_users.name AS owner_name,
26+
organizations.name AS organization_name,
27+
organizations.display_name AS organization_display_name,
28+
organizations.icon AS organization_icon,
29+
organizations.description AS organization_description,
30+
templates.name AS template_name,
31+
templates.display_name AS template_display_name,
32+
templates.icon AS template_icon,
33+
templates.description AS template_description,
34+
tasks.id AS task_id
35+
FROM ((((workspaces
36+
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
37+
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
38+
JOIN templates ON ((workspaces.template_id = templates.id)))
39+
LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id)));
40+
41+
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
DROP VIEW workspaces_expanded;
2+
3+
-- Expand more by including group_acl_display_info and
4+
-- user_acl_display_info columns with the actors' name and avatar.
5+
CREATE VIEW workspaces_expanded AS
6+
SELECT workspaces.id,
7+
workspaces.created_at,
8+
workspaces.updated_at,
9+
workspaces.owner_id,
10+
workspaces.organization_id,
11+
workspaces.template_id,
12+
workspaces.deleted,
13+
workspaces.name,
14+
workspaces.autostart_schedule,
15+
workspaces.ttl,
16+
workspaces.last_used_at,
17+
workspaces.dormant_at,
18+
workspaces.deleting_at,
19+
workspaces.automatic_updates,
20+
workspaces.favorite,
21+
workspaces.next_start_at,
22+
workspaces.group_acl,
23+
workspaces.user_acl,
24+
visible_users.avatar_url AS owner_avatar_url,
25+
visible_users.username AS owner_username,
26+
visible_users.name AS owner_name,
27+
organizations.name AS organization_name,
28+
organizations.display_name AS organization_display_name,
29+
organizations.icon AS organization_icon,
30+
organizations.description AS organization_description,
31+
templates.name AS template_name,
32+
templates.display_name AS template_display_name,
33+
templates.icon AS template_icon,
34+
templates.description AS template_description,
35+
tasks.id AS task_id,
36+
-- Workspace ACL actors' display info
37+
COALESCE((
38+
SELECT jsonb_object_agg(
39+
acl.key,
40+
jsonb_build_object(
41+
'name', COALESCE(g.name, ''),
42+
'avatar_url', COALESCE(g.avatar_url, '')
43+
)
44+
)
45+
FROM jsonb_each(workspaces.group_acl) AS acl
46+
LEFT JOIN groups g ON g.id = acl.key::uuid
47+
), '{}'::jsonb) AS group_acl_display_info,
48+
COALESCE((
49+
SELECT jsonb_object_agg(
50+
acl.key,
51+
jsonb_build_object(
52+
'name', COALESCE(vu.name, ''),
53+
'avatar_url', COALESCE(vu.avatar_url, '')
54+
)
55+
)
56+
FROM jsonb_each(workspaces.user_acl) AS acl
57+
LEFT JOIN visible_users vu ON vu.id = acl.key::uuid
58+
), '{}'::jsonb) AS user_acl_display_info
59+
FROM ((((workspaces
60+
JOIN visible_users ON ((workspaces.owner_id = visible_users.id)))
61+
JOIN organizations ON ((workspaces.organization_id = organizations.id)))
62+
JOIN templates ON ((workspaces.template_id = templates.id)))
63+
LEFT JOIN tasks ON ((workspaces.id = tasks.workspace_id)));
64+
65+
COMMENT ON VIEW workspaces_expanded IS 'Joins in the display name information such as username, avatar, and organization name.';

coderd/database/modelmethods.go

Lines changed: 25 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -658,7 +658,7 @@ func ConvertUserRows(rows []GetUsersRow) []User {
658658
return users
659659
}
660660

661-
func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
661+
func ConvertWorkspaceRows(rows []GetWorkspacesRow) ([]Workspace, error) {
662662
workspaces := make([]Workspace, len(rows))
663663
for i, r := range rows {
664664
workspaces[i] = Workspace{
@@ -679,6 +679,7 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
679679
Favorite: r.Favorite,
680680
OwnerAvatarUrl: r.OwnerAvatarUrl,
681681
OwnerUsername: r.OwnerUsername,
682+
OwnerName: r.OwnerName,
682683
OrganizationName: r.OrganizationName,
683684
OrganizationDisplayName: r.OrganizationDisplayName,
684685
OrganizationIcon: r.OrganizationIcon,
@@ -690,9 +691,31 @@ func ConvertWorkspaceRows(rows []GetWorkspacesRow) []Workspace {
690691
NextStartAt: r.NextStartAt,
691692
TaskID: r.TaskID,
692693
}
694+
695+
var err error
696+
697+
err = workspaces[i].UserACL.Scan(r.UserACL)
698+
if err != nil {
699+
return nil, xerrors.Errorf("scan user ACL %q: %w", r.UserACL, err)
700+
}
701+
err = workspaces[i].GroupACL.Scan(r.GroupACL)
702+
if err != nil {
703+
return nil, xerrors.Errorf("scan group ACL %q: %w", r.GroupACL, err)
704+
}
705+
706+
err = workspaces[i].UserACLDisplayInfo.Scan(r.UserACLDisplayInfo)
707+
if err != nil {
708+
return nil, xerrors.Errorf("scan user ACL display info %q: %w",
709+
r.UserACLDisplayInfo, err)
710+
}
711+
err = workspaces[i].GroupACLDisplayInfo.Scan(r.GroupACLDisplayInfo)
712+
if err != nil {
713+
return nil, xerrors.Errorf("scan group ACL display info %q: %w",
714+
r.GroupACLDisplayInfo, err)
715+
}
693716
}
694717

695-
return workspaces
718+
return workspaces, nil
696719
}
697720

698721
func (g Group) IsEveryone() bool {

coderd/database/modelqueries.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -323,6 +323,8 @@ func (q *sqlQuerier) GetAuthorizedWorkspaces(ctx context.Context, arg GetWorkspa
323323
&i.TemplateIcon,
324324
&i.TemplateDescription,
325325
&i.TaskID,
326+
&i.GroupACLDisplayInfo,
327+
&i.UserACLDisplayInfo,
326328
&i.TemplateVersionID,
327329
&i.TemplateVersionName,
328330
&i.LatestBuildCompletedAt,

0 commit comments

Comments
 (0)