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:
@@ -2041,6 +2041,104 @@ func (api *API) workspaceTimings(rw http.ResponseWriter, r *http.Request) {
|
||||
httpapi.Write(ctx, rw, http.StatusOK, timings)
|
||||
}
|
||||
|
||||
// @Summary Update workspace ACL
|
||||
// @ID update-workspace-acl
|
||||
// @Security CoderSessionToken
|
||||
// @Accept json
|
||||
// @Produce json
|
||||
// @Tags Workspaces
|
||||
// @Param workspace path string true "Workspace ID" format(uuid)
|
||||
// @Param request body codersdk.UpdateWorkspaceACL true "Update workspace ACL request"
|
||||
// @Success 204
|
||||
// @Router /workspaces/{workspace}/acl [patch]
|
||||
func (api *API) patchWorkspaceACL(rw http.ResponseWriter, r *http.Request) {
|
||||
var (
|
||||
ctx = r.Context()
|
||||
workspace = httpmw.WorkspaceParam(r)
|
||||
auditor = api.Auditor.Load()
|
||||
aReq, commitAudit = audit.InitRequest[database.WorkspaceTable](rw, &audit.RequestParams{
|
||||
Audit: *auditor,
|
||||
Log: api.Logger,
|
||||
Request: r,
|
||||
Action: database.AuditActionWrite,
|
||||
OrganizationID: workspace.OrganizationID,
|
||||
})
|
||||
)
|
||||
defer commitAudit()
|
||||
aReq.Old = workspace.WorkspaceTable()
|
||||
|
||||
var req codersdk.UpdateWorkspaceACL
|
||||
if !httpapi.Read(ctx, rw, r, &req) {
|
||||
return
|
||||
}
|
||||
|
||||
validErrs := validateWorkspaceACLPerms(ctx, api.Database, req.UserRoles, "user_roles")
|
||||
validErrs = append(validErrs, validateWorkspaceACLPerms(
|
||||
ctx,
|
||||
api.Database,
|
||||
req.GroupRoles,
|
||||
"group_roles",
|
||||
)...)
|
||||
|
||||
if len(validErrs) > 0 {
|
||||
httpapi.Write(ctx, rw, http.StatusBadRequest, codersdk.Response{
|
||||
Message: "Invalid request to update template metadata!",
|
||||
Validations: validErrs,
|
||||
})
|
||||
return
|
||||
}
|
||||
|
||||
err := api.Database.InTx(func(tx database.Store) error {
|
||||
var err error
|
||||
workspace, err = tx.GetWorkspaceByID(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get template by ID: %w", err)
|
||||
}
|
||||
|
||||
for id, role := range req.UserRoles {
|
||||
if role == codersdk.WorkspaceRoleDeleted {
|
||||
delete(workspace.UserACL, id)
|
||||
continue
|
||||
}
|
||||
workspace.UserACL[id] = database.WorkspaceACLEntry{
|
||||
Permissions: db2sdk.WorkspaceRoleActions(role),
|
||||
}
|
||||
}
|
||||
|
||||
for id, role := range req.GroupRoles {
|
||||
if role == codersdk.WorkspaceRoleDeleted {
|
||||
delete(workspace.GroupACL, id)
|
||||
continue
|
||||
}
|
||||
workspace.GroupACL[id] = database.WorkspaceACLEntry{
|
||||
Permissions: db2sdk.WorkspaceRoleActions(role),
|
||||
}
|
||||
}
|
||||
|
||||
err = tx.UpdateWorkspaceACLByID(ctx, database.UpdateWorkspaceACLByIDParams{
|
||||
ID: workspace.ID,
|
||||
UserACL: workspace.UserACL,
|
||||
GroupACL: workspace.GroupACL,
|
||||
})
|
||||
if err != nil {
|
||||
return xerrors.Errorf("update workspace ACL by ID: %w", err)
|
||||
}
|
||||
workspace, err = tx.GetWorkspaceByID(ctx, workspace.ID)
|
||||
if err != nil {
|
||||
return xerrors.Errorf("get updated workspace by ID: %w", err)
|
||||
}
|
||||
return nil
|
||||
}, nil)
|
||||
if err != nil {
|
||||
httpapi.InternalServerError(rw, err)
|
||||
return
|
||||
}
|
||||
|
||||
aReq.New = workspace.WorkspaceTable()
|
||||
|
||||
rw.WriteHeader(http.StatusNoContent)
|
||||
}
|
||||
|
||||
type workspaceData struct {
|
||||
templates []database.Template
|
||||
builds []codersdk.WorkspaceBuild
|
||||
@@ -2379,3 +2477,64 @@ func (api *API) publishWorkspaceAgentLogsUpdate(ctx context.Context, workspaceAg
|
||||
api.Logger.Warn(ctx, "failed to publish workspace agent logs update", slog.F("workspace_agent_id", workspaceAgentID), slog.Error(err))
|
||||
}
|
||||
}
|
||||
|
||||
func validateWorkspaceACLPerms(ctx context.Context, db database.Store, perms map[string]codersdk.WorkspaceRole, field string) []codersdk.ValidationError {
|
||||
// nolint:gocritic // Validate requires full read access to users and groups
|
||||
ctx = dbauthz.AsSystemRestricted(ctx)
|
||||
var validErrs []codersdk.ValidationError
|
||||
for idStr, role := range perms {
|
||||
if err := validateWorkspaceRole(role); err != nil {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: field, Detail: err.Error()})
|
||||
continue
|
||||
}
|
||||
|
||||
id, err := uuid.Parse(idStr)
|
||||
if err != nil {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: field, Detail: idStr + "is not a valid UUID."})
|
||||
continue
|
||||
}
|
||||
|
||||
switch field {
|
||||
case "user_roles":
|
||||
// TODO(lilac): put this back after Kirby button shenanigans are over
|
||||
// This could get slow if we get a ton of user perm updates.
|
||||
// _, err = db.GetUserByID(ctx, id)
|
||||
// if err != nil {
|
||||
// validErrs = append(validErrs, codersdk.ValidationError{Field: field, Detail: fmt.Sprintf("Failed to find resource with ID %q: %v", idStr, err.Error())})
|
||||
// continue
|
||||
// }
|
||||
case "group_roles":
|
||||
// This could get slow if we get a ton of group perm updates.
|
||||
_, err = db.GetGroupByID(ctx, id)
|
||||
if err != nil {
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: field, Detail: fmt.Sprintf("Failed to find resource with ID %q: %v", idStr, err.Error())})
|
||||
continue
|
||||
}
|
||||
default:
|
||||
validErrs = append(validErrs, codersdk.ValidationError{Field: field, Detail: "invalid field"})
|
||||
}
|
||||
}
|
||||
|
||||
return validErrs
|
||||
}
|
||||
|
||||
func validateWorkspaceRole(role codersdk.WorkspaceRole) error {
|
||||
actions := db2sdk.WorkspaceRoleActions(role)
|
||||
if len(actions) == 0 && role != codersdk.WorkspaceRoleDeleted {
|
||||
return xerrors.Errorf("role %q is not a valid Workspace role", role)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// TODO: This will go here
|
||||
// func convertToWorkspaceRole(actions []policy.Action) codersdk.TemplateRole {
|
||||
// switch {
|
||||
// case len(actions) == 2 && slice.SameElements(actions, []policy.Action{policy.ActionUse, policy.ActionRead}):
|
||||
// return codersdk.TemplateRoleUse
|
||||
// case len(actions) == 1 && actions[0] == policy.WildcardSymbol:
|
||||
// return codersdk.TemplateRoleAdmin
|
||||
// }
|
||||
|
||||
// return ""
|
||||
// }
|
||||
|
||||
Reference in New Issue
Block a user