mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
feat: add workspace sharing page (#19107)
This commit is contained in:
@@ -24,6 +24,7 @@ import (
|
||||
"github.com/coder/coder/v2/coderd/rbac/policy"
|
||||
"github.com/coder/coder/v2/coderd/render"
|
||||
"github.com/coder/coder/v2/coderd/util/ptr"
|
||||
"github.com/coder/coder/v2/coderd/util/slice"
|
||||
"github.com/coder/coder/v2/coderd/workspaceapps/appurl"
|
||||
"github.com/coder/coder/v2/codersdk"
|
||||
"github.com/coder/coder/v2/provisionersdk/proto"
|
||||
@@ -781,6 +782,29 @@ func TemplateRoleActions(role codersdk.TemplateRole) []policy.Action {
|
||||
return []policy.Action{}
|
||||
}
|
||||
|
||||
func WorkspaceRoleActions(role codersdk.WorkspaceRole) []policy.Action {
|
||||
switch role {
|
||||
case codersdk.WorkspaceRoleAdmin:
|
||||
return slice.Omit(
|
||||
// Small note: This intentionally includes "create" because it's sort of
|
||||
// double purposed as "can edit ACL". That's maybe a bit "incorrect", but
|
||||
// it's what templates do already and we're copying that implementation.
|
||||
rbac.ResourceWorkspace.AvailableActions(),
|
||||
// Don't let anyone delete something they can't recreate.
|
||||
policy.ActionDelete,
|
||||
)
|
||||
case codersdk.WorkspaceRoleUse:
|
||||
return []policy.Action{
|
||||
policy.ActionApplicationConnect,
|
||||
policy.ActionRead,
|
||||
policy.ActionSSH,
|
||||
policy.ActionWorkspaceStart,
|
||||
policy.ActionWorkspaceStop,
|
||||
}
|
||||
}
|
||||
return []policy.Action{}
|
||||
}
|
||||
|
||||
func ConnectionLogConnectionTypeFromAgentProtoConnectionType(typ agentproto.Connection_Type) (database.ConnectionType, error) {
|
||||
switch typ {
|
||||
case agentproto.Connection_SSH:
|
||||
|
||||
@@ -4919,6 +4919,18 @@ func (q *querier) UpdateWorkspace(ctx context.Context, arg database.UpdateWorksp
|
||||
return updateWithReturn(q.log, q.auth, fetch, q.db.UpdateWorkspace)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceACLByID(ctx context.Context, arg database.UpdateWorkspaceACLByIDParams) error {
|
||||
fetch := func(ctx context.Context, arg database.UpdateWorkspaceACLByIDParams) (database.WorkspaceTable, error) {
|
||||
w, err := q.db.GetWorkspaceByID(ctx, arg.ID)
|
||||
if err != nil {
|
||||
return database.WorkspaceTable{}, err
|
||||
}
|
||||
return w.WorkspaceTable(), nil
|
||||
}
|
||||
|
||||
return fetchAndExec(q.log, q.auth, policy.ActionCreate, fetch, q.db.UpdateWorkspaceACLByID)(ctx, arg)
|
||||
}
|
||||
|
||||
func (q *querier) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg database.UpdateWorkspaceAgentConnectionByIDParams) error {
|
||||
if err := q.authorizeContext(ctx, policy.ActionUpdate, rbac.ResourceSystem); err != nil {
|
||||
return err
|
||||
|
||||
@@ -2146,6 +2146,22 @@ func (s *MethodTestSuite) TestWorkspace() {
|
||||
// no asserts here because SQLFilter
|
||||
check.Args([]uuid.UUID{}, emptyPreparedAuthorized{}).Asserts()
|
||||
}))
|
||||
s.Run("UpdateWorkspaceACLByID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
tpl := dbgen.Template(s.T(), db, database.Template{
|
||||
OrganizationID: o.ID,
|
||||
CreatedBy: u.ID,
|
||||
})
|
||||
ws := dbgen.Workspace(s.T(), db, database.WorkspaceTable{
|
||||
OwnerID: u.ID,
|
||||
OrganizationID: o.ID,
|
||||
TemplateID: tpl.ID,
|
||||
})
|
||||
check.Args(database.UpdateWorkspaceACLByIDParams{
|
||||
ID: ws.ID,
|
||||
}).Asserts(ws, policy.ActionCreate)
|
||||
}))
|
||||
s.Run("GetLatestWorkspaceBuildByWorkspaceID", s.Subtest(func(db database.Store, check *expects) {
|
||||
u := dbgen.User(s.T(), db, database.User{})
|
||||
o := dbgen.Organization(s.T(), db, database.Organization{})
|
||||
|
||||
@@ -3029,6 +3029,13 @@ func (m queryMetricsStore) UpdateWorkspace(ctx context.Context, arg database.Upd
|
||||
return workspace, err
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateWorkspaceACLByID(ctx context.Context, arg database.UpdateWorkspaceACLByIDParams) error {
|
||||
start := time.Now()
|
||||
r0 := m.s.UpdateWorkspaceACLByID(ctx, arg)
|
||||
m.queryLatencies.WithLabelValues("UpdateWorkspaceACLByID").Observe(time.Since(start).Seconds())
|
||||
return r0
|
||||
}
|
||||
|
||||
func (m queryMetricsStore) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg database.UpdateWorkspaceAgentConnectionByIDParams) error {
|
||||
start := time.Now()
|
||||
err := m.s.UpdateWorkspaceAgentConnectionByID(ctx, arg)
|
||||
|
||||
@@ -6461,6 +6461,20 @@ func (mr *MockStoreMockRecorder) UpdateWorkspace(ctx, arg any) *gomock.Call {
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspace", reflect.TypeOf((*MockStore)(nil).UpdateWorkspace), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceACLByID mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceACLByID(ctx context.Context, arg database.UpdateWorkspaceACLByIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
ret := m.ctrl.Call(m, "UpdateWorkspaceACLByID", ctx, arg)
|
||||
ret0, _ := ret[0].(error)
|
||||
return ret0
|
||||
}
|
||||
|
||||
// UpdateWorkspaceACLByID indicates an expected call of UpdateWorkspaceACLByID.
|
||||
func (mr *MockStoreMockRecorder) UpdateWorkspaceACLByID(ctx, arg any) *gomock.Call {
|
||||
mr.mock.ctrl.T.Helper()
|
||||
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWorkspaceACLByID", reflect.TypeOf((*MockStore)(nil).UpdateWorkspaceACLByID), ctx, arg)
|
||||
}
|
||||
|
||||
// UpdateWorkspaceAgentConnectionByID mocks base method.
|
||||
func (m *MockStore) UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg database.UpdateWorkspaceAgentConnectionByIDParams) error {
|
||||
m.ctrl.T.Helper()
|
||||
|
||||
@@ -276,7 +276,9 @@ func (w WorkspaceTable) RBACObject() rbac.Object {
|
||||
|
||||
return rbac.ResourceWorkspace.WithID(w.ID).
|
||||
InOrg(w.OrganizationID).
|
||||
WithOwner(w.OwnerID.String())
|
||||
WithOwner(w.OwnerID.String()).
|
||||
WithGroupACL(w.GroupACL.RBACACL()).
|
||||
WithACLUserList(w.UserACL.RBACACL())
|
||||
}
|
||||
|
||||
func (w WorkspaceTable) DormantRBAC() rbac.Object {
|
||||
|
||||
@@ -628,6 +628,7 @@ type sqlcQuerier interface {
|
||||
UpdateUserThemePreference(ctx context.Context, arg UpdateUserThemePreferenceParams) (UserConfig, error)
|
||||
UpdateVolumeResourceMonitor(ctx context.Context, arg UpdateVolumeResourceMonitorParams) error
|
||||
UpdateWorkspace(ctx context.Context, arg UpdateWorkspaceParams) (WorkspaceTable, error)
|
||||
UpdateWorkspaceACLByID(ctx context.Context, arg UpdateWorkspaceACLByIDParams) error
|
||||
UpdateWorkspaceAgentConnectionByID(ctx context.Context, arg UpdateWorkspaceAgentConnectionByIDParams) error
|
||||
UpdateWorkspaceAgentLifecycleStateByID(ctx context.Context, arg UpdateWorkspaceAgentLifecycleStateByIDParams) error
|
||||
UpdateWorkspaceAgentLogOverflowByID(ctx context.Context, arg UpdateWorkspaceAgentLogOverflowByIDParams) error
|
||||
|
||||
@@ -20872,6 +20872,27 @@ func (q *sqlQuerier) UpdateWorkspace(ctx context.Context, arg UpdateWorkspacePar
|
||||
return i, err
|
||||
}
|
||||
|
||||
const updateWorkspaceACLByID = `-- name: UpdateWorkspaceACLByID :exec
|
||||
UPDATE
|
||||
workspaces
|
||||
SET
|
||||
group_acl = $1,
|
||||
user_acl = $2
|
||||
WHERE
|
||||
id = $3
|
||||
`
|
||||
|
||||
type UpdateWorkspaceACLByIDParams struct {
|
||||
GroupACL WorkspaceACL `db:"group_acl" json:"group_acl"`
|
||||
UserACL WorkspaceACL `db:"user_acl" json:"user_acl"`
|
||||
ID uuid.UUID `db:"id" json:"id"`
|
||||
}
|
||||
|
||||
func (q *sqlQuerier) UpdateWorkspaceACLByID(ctx context.Context, arg UpdateWorkspaceACLByIDParams) error {
|
||||
_, err := q.db.ExecContext(ctx, updateWorkspaceACLByID, arg.GroupACL, arg.UserACL, arg.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
const updateWorkspaceAutomaticUpdates = `-- name: UpdateWorkspaceAutomaticUpdates :exec
|
||||
UPDATE
|
||||
workspaces
|
||||
|
||||
@@ -873,3 +873,12 @@ GROUP BY workspaces.id, workspaces.name, latest_build.job_status, latest_build.j
|
||||
|
||||
-- name: GetWorkspacesByTemplateID :many
|
||||
SELECT * FROM workspaces WHERE template_id = $1 AND deleted = false;
|
||||
|
||||
-- name: UpdateWorkspaceACLByID :exec
|
||||
UPDATE
|
||||
workspaces
|
||||
SET
|
||||
group_acl = @group_acl,
|
||||
user_acl = @user_acl
|
||||
WHERE
|
||||
id = @id;
|
||||
|
||||
@@ -91,6 +91,17 @@ func (t *WorkspaceACL) Scan(src interface{}) error {
|
||||
return xerrors.Errorf("unexpected type %T", src)
|
||||
}
|
||||
|
||||
//nolint:revive
|
||||
func (w WorkspaceACL) RBACACL() map[string][]policy.Action {
|
||||
// Convert WorkspaceACL to a map of string to []policy.Action.
|
||||
// This is used for RBAC checks.
|
||||
rbacACL := make(map[string][]policy.Action, len(w))
|
||||
for id, entry := range w {
|
||||
rbacACL[id] = entry.Permissions
|
||||
}
|
||||
return rbacACL
|
||||
}
|
||||
|
||||
func (t WorkspaceACL) Value() (driver.Value, error) {
|
||||
return json.Marshal(t)
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user