authorize performs build authorization pre-checks using the provided authFunc
(authFunc func(action policy.Action, object rbac.Objecter) bool)
| 1252 | |
| 1253 | // authorize performs build authorization pre-checks using the provided authFunc |
| 1254 | func (b *Builder) authorize(authFunc func(action policy.Action, object rbac.Objecter) bool) error { |
| 1255 | // Doing this up front saves a lot of work if the user doesn't have permission. |
| 1256 | // This is checked again in the dbauthz layer, but the check is cached |
| 1257 | // and will be a noop later. |
| 1258 | var action policy.Action |
| 1259 | switch b.trans { |
| 1260 | case database.WorkspaceTransitionDelete: |
| 1261 | action = policy.ActionDelete |
| 1262 | case database.WorkspaceTransitionStart: |
| 1263 | action = policy.ActionWorkspaceStart |
| 1264 | if b.workspace.DormantAt.Valid { |
| 1265 | // Dormant workspaces can't be started directly; they are |
| 1266 | // first "woken" by unsetting dormancy, which makes the |
| 1267 | // workspace.start permission apply. |
| 1268 | action = policy.ActionUpdate |
| 1269 | } |
| 1270 | case database.WorkspaceTransitionStop: |
| 1271 | action = policy.ActionWorkspaceStop |
| 1272 | default: |
| 1273 | msg := fmt.Sprintf("Transition %q not supported.", b.trans) |
| 1274 | return BuildError{http.StatusBadRequest, msg, xerrors.New(msg)} |
| 1275 | } |
| 1276 | |
| 1277 | // Try default workspace authorization first |
| 1278 | authorized := authFunc(action, b.workspace) |
| 1279 | |
| 1280 | // Special handling for prebuilt workspace deletion |
| 1281 | if !authorized && action == policy.ActionDelete && b.workspace.IsPrebuild() { |
| 1282 | authorized = authFunc(action, b.workspace.AsPrebuild()) |
| 1283 | } |
| 1284 | |
| 1285 | if !authorized { |
| 1286 | if authFunc(policy.ActionRead, b.workspace) { |
| 1287 | // If the user can read the workspace, but not delete/create/update. Show |
| 1288 | // a more helpful error. They are allowed to know the workspace exists. |
| 1289 | return BuildError{ |
| 1290 | Status: http.StatusForbidden, |
| 1291 | Message: fmt.Sprintf("You do not have permission to %s this workspace.", action), |
| 1292 | Wrapped: xerrors.New(httpapi.ResourceForbiddenResponse.Detail), |
| 1293 | } |
| 1294 | } |
| 1295 | // We use the same wording as the httpapi to avoid leaking the existence of the workspace |
| 1296 | return BuildError{http.StatusNotFound, httpapi.ResourceNotFoundResponse.Message, xerrors.New(httpapi.ResourceNotFoundResponse.Message)} |
| 1297 | } |
| 1298 | |
| 1299 | template, err := b.getTemplate() |
| 1300 | if err != nil { |
| 1301 | return BuildError{http.StatusInternalServerError, "failed to fetch template", err} |
| 1302 | } |
| 1303 | |
| 1304 | // If custom state, deny request since user could be corrupting or leaking |
| 1305 | // cloud state. |
| 1306 | if b.state.explicit != nil || b.state.orphan { |
| 1307 | if !authFunc(policy.ActionUpdate, template.RBACObject()) { |
| 1308 | return BuildError{http.StatusForbidden, "Only template managers may provide custom state", xerrors.New("Only template managers may provide custom state")} |
| 1309 | } |
| 1310 | } |
| 1311 |
no test coverage detected