Select Git revision
routerVMs.go
routerVMs.go 25.24 KiB
package router
import (
"io"
"os"
"net/http"
"path/filepath"
"nexus-common/caps"
vmc "nexus-common/vm"
"nexus-server/vms"
"nexus-server/users"
"nexus-server/paths"
"github.com/google/uuid"
"github.com/labstack/echo/v4"
"github.com/go-playground/validator/v10"
)
type (
RouterVMs struct {
users *users.Users
vms *vms.VMs
}
vmActionFn func(c echo.Context, vm *vms.VM) error
vmsListableFn func(c echo.Context, vm *vms.VM) error
)
func NewRouterVMs() *RouterVMs {
return &RouterVMs{ users: users.GetUsersInstance(), vms: vms.GetVMsInstance() }
}
// Returns VMs that can be listed.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_LIST_ANY: returns all VMs.
// VM access cap: CAP_VM_LIST: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetListableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_LIST_ANY, caps.CAP_VM_LIST, func(vm *vms.VM) bool {
return true
})
}
// Returns VMs that are running and that can be attached to.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_LIST_ANY: returns all running VMs.
// VM access cap: CAP_VM_LIST: returns all running VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/attach -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetAttachableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_LIST_ANY, caps.CAP_VM_LIST, func(vm *vms.VM) bool {
return vm.IsRunning()
})
}
// Returns VMs that are stopped and that can be deleted.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_DESTROY_ANY: returns all VMs.
// VM access cap: CAP_VM_DESTROY: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/del -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetDeletableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_DESTROY_ANY, caps.CAP_VM_DESTROY, func(vm *vms.VM) bool {
return !vm.IsRunning()
})
}
// Returns VMs that are stopped and that can be started.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_START_ANY: returns all VMs.
// VM access cap: CAP_VM_START: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/start -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetStartableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_START_ANY, caps.CAP_VM_START, func(vm *vms.VM) bool {
return !vm.IsRunning()
})
}
// Returns VMs that are running and that can be stopped.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_STOP_ANY: returns all VMs.
// VM access cap: CAP_VM_STOP: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/stop -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetStoppableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_STOP_ANY, caps.CAP_VM_STOP, func(vm *vms.VM) bool {
return vm.IsRunning()
})
}
// Returns VMs that are running and that can be rebooted.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_REBOOT_ANY: returns all VMs.
// VM access cap: CAP_VM_REBOOT: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/reboot -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetRebootableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_REBOOT_ANY, caps.CAP_VM_REBOOT, func(vm *vms.VM) bool {
return vm.IsRunning()
})
}
// Returns VMs that can be edited.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_EDIT_ANY: returns all VMs.
// VM access cap: CAP_VM_EDIT: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/edit -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetEditableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_EDIT_ANY, caps.CAP_VM_EDIT, func(vm *vms.VM) bool {
return !vm.IsRunning()
})
}
// Returns VMs that can have their access set or deleted.
// Requires to be the VM's owner, or BOTH capabilities:
// User cap: CAP_VM_SET_ACCESS
// VM access cap: CAP_VM_SET_ACCESS
// curl --cacert ca.pem -X GET https://localhost:1077/vms/editaccess -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetEditableVMAccessVMs(c echo.Context) error {
// Retrieves logged user from context.
user, err := getLoggedUser(r.users, c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMs(
func(vm *vms.VM) bool {
// First, checks if the user is the VM's owner
if vm.IsOwner(user.Email) {
return !vm.IsRunning()
} else {
// Then, checks that user has CAP_VM_SET_ACCESS and also that
// VM access has CAP_VM_SET_ACCESS set for the user
if user.HasCapability(caps.CAP_VM_SET_ACCESS) {
capabilities, exists := vm.GetAccess()[user.Email]
if exists {
_, visible := capabilities[caps.CAP_VM_SET_ACCESS]
return visible && !vm.IsRunning()
} else {
return false
}
} else {
return false
}
}
}), " ")
}
// Returns VMs that can have a directory exported.
// Requires to be the VM's owner, or either capability:
// User cap: VM_READFS_ANY: returns all VMs.
// VM access cap: VM_READFS: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/exportdir -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetDirExportableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_READFS_ANY, caps.CAP_VM_READFS, func(vm *vms.VM) bool {
return true
})
}
// Returns VMs that can have files imported (i.e. copied) into their filesystem.
// Requires to be the VM's owner, or either capability:
// User cap: VM_WRITEFS_ANY: returns all VMs.
// VM access cap: VM_WRITEFS: returns all VMs with this cap for the logged user.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/importfiles -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)GetFilesImportableVMs(c echo.Context) error {
return r.performVMsList(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(vm *vms.VM) bool {
return true
})
}
// Creates a VM.
// Requires this capability:
// User cap: CAP_VM_CREATE.
// curl --cacert ca.pem -X POST https://localhost:1077/vms -H 'Content-Type: application/json' -d '{"name":"Systems Programming 2022","cpus":2,"ram":4096,"nic":"none","usbDevs":["1307:0165","1234:abcd"],"template":"Xubuntu_22.04"}' -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)CreateVM(c echo.Context) error {
// Retrieves logged user from context.
user, err := getLoggedUser(r.users, c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
if !user.HasCapability(caps.CAP_VM_CREATE) {
return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
}
// Deserializes and validates client's parameters.
type Parameters struct {
Name string `json:"name" validate:"required,min=4,max=256"`
Cpus int `json:"cpus" validate:"required,gte=1,lte=16"`
Ram int `json:"ram" validate:"required,gte=512,lte=32768"`
Nic vmc.NicType `json:"nic" validate:"required`
UsbDevs []string `json:"usbDevs" validate:"required`
TemplateID uuid.UUID `json:"templateID" validate:"required"`
}
p := new(Parameters)
if err := decodeJson(c, &p); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Creates a new VM from the client's parameters.
vm, err := vms.NewVM(user.Email, p.Name, p.Cpus, p.Ram, p.Nic, p.UsbDevs, p.TemplateID, user.Email)
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if err = r.vms.AddVM(vm); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.JSONPretty(http.StatusCreated, vm.SerializeToNetwork(), " ")
}
// Deletes a VM based on its ID.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_DESTROY_ANY: any VM can be deleted.
// VM access cap: CAP_VM_DESTROY: any of the VMs with this cap for the logged user can be deleted.
// curl --cacert ca.pem -X DELETE https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59 -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)DeleteVMByID(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_DESTROY_ANY, caps.CAP_VM_DESTROY, func(c echo.Context, vm *vms.VM) error {
if err := r.vms.DeleteVM(vm.GetID()); err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
})
}
// Starts a VM based on its ID.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_START_ANY: any VM can be started.
// VM access cap: CAP_VM_START: any of the VMs with this cap for the logged user can be started.
// curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/start -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)StartVM(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_START_ANY, caps.CAP_VM_START, func(c echo.Context, vm *vms.VM) error {
_, _, err := r.vms.StartVM(vm.GetID())
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
})
}
// Kills a VM based on its ID.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_STOP_ANY: any VM can be killed.
// VM access cap: CAP_VM_STOP: any of the VMs with this cap for the logged user can be killed.
// curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/stop -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)KillVM(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_STOP_ANY, caps.CAP_VM_STOP, func(c echo.Context, vm *vms.VM) error {
if err := r.vms.KillVM(vm.GetID()); err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
})
}
// Gracefully shutdown a VM based on its ID.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_STOP_ANY: any VM can be shutdown.
// VM access cap: CAP_VM_STOP: any of the VMs with this cap for the logged user can be shutdown.
// curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/shutdown -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)ShutdownVM(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_STOP_ANY, caps.CAP_VM_STOP, func(c echo.Context, vm *vms.VM) error {
if err := r.vms.ShutdownVM(vm.GetID()); err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
})
}
// Reboot a VM based on its ID.
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_REBOOT_ANY: any VM can be rebooted.
// VM access cap: CAP_VM_REBOOT: any of the VMs with this cap for the logged user can be rebooted.
// curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/stop -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)RebootVM(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_REBOOT_ANY, caps.CAP_VM_REBOOT, func(c echo.Context, vm *vms.VM) error {
if err := r.vms.RebootVM(vm.GetID()); err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
})
}
// Edit a VM' specs: name, cpus, ram, nic
// Requires to be the VM's owner, or either capability:
// User cap: CAP_VM_EDIT_ANY: any VM can be edited.
// VM access cap: CAP_VM_EDIT: any of the VMs with this cap for the logged user can be edited.
// curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59 -H 'Content-Type: application/json' -d '{"name":"Edited VM","cpus":1,"ram":2048,"nic":"user","usbDevs":["1307:0165","1234:abcd"]}' -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)EditVMByID(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_EDIT_ANY, caps.CAP_VM_EDIT, func(c echo.Context, vm *vms.VM) error {
// Deserializes and validates the client's parameters.
// Given these parameters are optional, we can't use a validator on them.
// Validation is performed in vm.EditVM() instead.
type Parameters struct {
Name string `json:"name" validate:"required,min=4,max=256"`
Cpus int `json:"cpus" validate:"required,gte=1,lte=16"`
Ram int `json:"ram" validate:"required,gte=512,lte=32768"`
Nic vmc.NicType `json:"nic" validate:"required`
UsbDevs []string `json:"usbDevs" validate:"required`
}
p := new(Parameters)
if err := decodeJson(c, &p); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if err := r.vms.EditVM(vm.GetID(), p.Name, p.Cpus, p.Ram, p.Nic, p.UsbDevs); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.JSONPretty(http.StatusOK, vm.SerializeToNetwork(), " ")
})
}
// Set a VM access for a given user.
// Requires to be the VM's owner, or BOTH capabilities:
// User cap: CAP_VM_SET_ACCESS
// VM access cap: CAP_VM_SET_ACCESS
// curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/caps/janedoes@nexus.org -H 'Content-Type: application/json' -d '{"caps":{"VM_LIST":1,"VM_START":1,"VM_STOP":1}}' -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)SetVMAccessForUser(c echo.Context) error {
// Retrieves logged user from context and checks she/he has sufficient capabilities.
user, err := getLoggedUser(r.users, c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
// Checks the user for which to set the VM Access actually exists.
email := c.Param("email")
_, err = r.users.GetUserByEmail(email)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
// Retrieves the vmID of the VM to modify.
vmID, err := uuid.Parse(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Retrieves the VM to modify.
vm, err := r.vms.GetVM(vmID)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
// First, check that the logged user is the VM's owner.
if !vm.IsOwner(user.Email) {
// Next, checks the logged user has the VM_SET_ACCESS capability.
if !user.HasCapability(caps.CAP_VM_SET_ACCESS) {
return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
}
}
// Deserializes and validates the client's parameters.
type Parameters struct {
Access caps.Capabilities `json:"access" validate:"required"`
}
params := new(Parameters)
if err := decodeJson(c, ¶ms); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if err := validator.New().Struct(params); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
if err = r.vms.SetVMAccess(vmID, user.Email, email, params.Access); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
}
// Delete a VM Access for a given user.
// Requires to be the VM's owner, or BOTH capabilities:
// User cap: CAP_VM_SET_ACCESS
// VM access cap: CAP_VM_SET_ACCESS
// curl --cacert ca.pem -X DELETE https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/caps/janedoes@nexus.org -H 'Content-Type: application/json' -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)DeleteVMAccessForUser(c echo.Context) error {
// Retrieves logged user from context and checks she/he has sufficient capabilities.
user, err := getLoggedUser(r.users, c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
// Retrieves the vmID of the VM to modify.
vmID, err := uuid.Parse(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Retrieves the VM to modify.
vm, err := r.vms.GetVM(vmID)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
// Does not check that the user to remove the VM access for actually exists.
// Indeed, it might have been deleted.
email := c.Param("email")
// First, check that the logged user is the VM's owner.
if !vm.IsOwner(user.Email) {
// Next, checks the logged user has the VM_SET_ACCESS capability.
if !user.HasCapability(caps.CAP_VM_SET_ACCESS) {
return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
}
}
if err = r.vms.DeleteVMAccess(vmID, user.Email, email); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.JSONPretty(http.StatusOK, jsonMsg("OK"), " ")
}
// Exports a VM's directory into a compressed archive.
// Requires to be the VM's owner, or either capability:
// User cap: VM_READFS_ANY: any VM can have their filesystem read.
// VM access cap: VM_READFS: any of the VMs with this cap for the logged user can have their filesystem read.
// curl --cacert ca.pem -X GET https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/exportdir -H 'Content-Type: application/json' -d '{"dir":"absolute_path_to_dir"}' -H "Authorization: Bearer <AccessToken>" --output dir.tar
func (r *RouterVMs)ExportVMDir(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_READFS_ANY, caps.CAP_VM_READFS, func(c echo.Context, vm *vms.VM) error {
// Deserializes and validates the client's parameter.
type Parameters struct {
Dir string `json:"dir"`
}
p := new(Parameters)
if err := decodeJson(c, &p); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
// Creates a unique tar filename.
tmpDir := paths.GetInstance().TmpDir
tarGzFile := filepath.Join(tmpDir, "exportdir_"+vm.GetID().String()+".tar.gz")
// Extracts VM's p.Dir directory into tarGzFile on the host.
if err := r.vms.ExportVMFiles(vm, p.Dir, tarGzFile); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, "Failed extracting VM's dir")
}
// Sends the archive back to the client and once completed, deletes it.
defer func(file string) {
if err := os.Remove(file); err != nil {
log.Error("Failed removing archive of extracted VM dir: "+err.Error())
}
}(tarGzFile)
return c.File(tarGzFile)
})
}
// Import files into a VM's filesystem, in a specified directory. The file tree is received in a tar.gz archive.
// Requires to be the VM's owner, or either capability:
// User cap: VM_WRITEFS_ANY: any VM can import the file tree.
// VM access cap: VM_WRITEFS: any of the VMs with this cap for the logged user can import the file tree.
// curl --cacert ca.pem -X POST https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/importfiles -H 'Content-Type: multipart/form-data' -F dir="/home/nexus" -F file=@files.tar -H "Authorization: Bearer <AccessToken>"
func (r *RouterVMs)ImportFilesToVM(c echo.Context) error {
return r.performVMAction(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(c echo.Context, vm *vms.VM) error {
// Retrieves the various client arguments.
vmDir := c.FormValue("vmDir")
// Retrieves the tar.gz archive (uploadedtarGzFile).
tarGzFile, err := c.FormFile("file")
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
src, err := tarGzFile.Open()
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
defer src.Close()
tmpDir := paths.GetInstance().TmpDir
uuid, err := uuid.NewRandom()
if err != nil {
log.Error("Failed creating random UUID: "+err.Error())
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
uploadedtarGzFile := filepath.Join(tmpDir, "upload_"+uuid.String()+".tar")
dst, err := os.Create(uploadedtarGzFile)
if err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
defer dst.Close()
defer os.Remove(uploadedtarGzFile)
if _, err = io.Copy(dst, src); err != nil {
return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
}
// Copy the archive's files into the VM
// IMPORTANT: check the VM is NOT running!
if err = r.vms.ImportFilesToVM(vm, uploadedtarGzFile, vmDir); err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
return c.JSONPretty(http.StatusCreated, jsonMsg("OK"), " ")
})
}
// Helper function that returns a list of serialized VMs that match either:
// - the logged user has the userCapabilityAny capability.
// - the logged user is the VM's owner.
// - the VM access for the logged user matches the vmAccessCapability capability.
// Also, VMs for which cond is false are filtered out.
func (r *RouterVMs)performVMsList(c echo.Context, userCapabilityAny, vmAccessCapability string, cond vms.VMKeeperFn) error {
// Retrieves logged user from context.
user, err := getLoggedUser(r.users, c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
// If the logged user has the XX_ANY capability (userCapabilityAny), returns all VMs.
if user.HasCapability(userCapabilityAny) {
// Returns all VMs that pass the condition.
return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMs(cond), " ")
} else {
// Returns all VMs:
// - owned by the logged user
// - for which the logged user has the specified capability (vmAccessCapability) in the VM's access
return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMs(
func(vm *vms.VM) bool {
if vm.IsOwner(user.Email) {
return cond(vm)
} else {
capabilities, exists := vm.GetAccess()[user.Email]
if exists {
_, visible := capabilities[vmAccessCapability]
return visible && cond(vm)
} else {
return false
}
}
}), " ")
}
}
// Helper function that performs an action on a VM based either on:
// - the logged user is the VM's owner (in this case, user has all VM access capabilities).
// - the logged user has the userCapabilityAny capability.
// - the VM access for the logged user matches the vmAccessCapability capability.
func (r *RouterVMs)performVMAction(c echo.Context, userCapabilityAny, vmAccessCapability string, action vmActionFn) error {
// Retrieves the VM on which to perform the action.
id, err := uuid.Parse(c.Param("id"))
if err != nil {
return echo.NewHTTPError(http.StatusBadRequest, err.Error())
}
vm, err := r.vms.GetVM(id)
if err != nil {
return echo.NewHTTPError(http.StatusNotFound, err.Error())
}
// Retrieves the logged user from context.
user, err := getLoggedUser(r.users, c)
if err != nil {
return echo.NewHTTPError(http.StatusUnauthorized, err.Error())
}
// First, checks if the user is the VM's owner
if vm.IsOwner(user.Email) {
return action(c, vm)
} else if user.HasCapability(userCapabilityAny) {
// Next, checks if the user has the XX_ANY capability
return action(c, vm)
} else {
// Finally, check if the VM access for the logged user matches the required capability
userCaps, exists := vm.GetAccess()[user.Email]
if !exists {
return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
}
_, hasAccess := userCaps[vmAccessCapability]
if !hasAccess {
return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
}
return action(c, vm)
}
}