Skip to content
Snippets Groups Projects
Select Git revision
  • 60abdc968e1c264bbb5c3cca0e6e0db30ff88b7b
  • live_exam_os_ubuntu default protected
2 results

routerVMs.go

Blame
  • routerVMs.go 20.22 KiB
    package router
    
    import (
    	"io"
    	"os"
    	"net/http"
    	"path/filepath"
    	"nexus-server/vms"
    	"nexus-server/caps"	
    	"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 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 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 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 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 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 can be edited.
    // Requires 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 true
    	})
    }
    
    // Returns VMs that can have their access set or deleted.
    // Requires 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())
    	}
    
    	if user.HasCapability(caps.CAP_VM_SET_ACCESS) {
    		// Returns all VMs for which VM access for the logged user matches CAP_VM_SET_ACCESS.
    		return c.JSONPretty(http.StatusOK, r.vms.GetVMs(
    			func(vm vms.VM) bool {
    				capabilities, exists := vm.Access[user.Email]
    				if exists {
    					_, visible := capabilities[caps.CAP_VM_SET_ACCESS]
    					return visible
    				} else {
    					return false
    				}
    			  }), "    ")
    	} else {
    		return c.JSONPretty(http.StatusOK, []vms.VM{}, "    ")
    	}
    }
    
    // Returns VMs that can have a directory exported.
    // Requires 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 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","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 vms.NicType       `json:"nic"        validate:"required"`  // "none" or "user"
    		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.
    	// The user creating the VM automatically gets all the VM access capabilities (see caps.VMAccessCaps for the list).
    	vm, err := vms.NewVM(user.Email, caps.VMAccessCaps, p.Name, p.Cpus, p.Ram, p.Nic, p.TemplateID)
    	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, "    ")
    }
    
    // Deletes a VM based on its ID.
    // Requires 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.ID); err != nil {
    			return echo.NewHTTPError(http.StatusNotFound, err.Error())
    		}
    		return c.JSONPretty(http.StatusOK, jsonMsg("OK"), "    ")
    	})
    }
    
    // Starts a VM based on its ID.
    // Requires 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.ID)
    		if err != nil {
    			return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    		}
    		return c.JSONPretty(http.StatusOK, jsonMsg("OK"), "    ")
    	})
    }
    
    // Stops (by force) a VM based on its ID.
    // Requires either capability:
    // User cap: CAP_VM_STOP_ANY: any VM can be stopped.
    // VM access cap: CAP_VM_STOP: any of the VMs with this cap for the logged user can be stopped.
    // curl --cacert ca.pem -X PUT https://localhost:1077/vms/e41f3556-ca24-4658-bd79-8c85bd6bff59/stop -H "Authorization: Bearer <AccessToken>"
    func (r *RouterVMs)StopVM(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.StopVM(vm.ID); err != nil {
    			return echo.NewHTTPError(http.StatusNotFound, err.Error())
    		}
    		return c.JSONPretty(http.StatusOK, jsonMsg("OK"), "    ")
    	})
    }
    
    // Gracefully stops a VM based on its ID.
    // Requires either capability:
    // User cap: CAP_VM_STOP_ANY: any VM can be stopped.
    // VM access cap: CAP_VM_STOP: any of the VMs with this cap for the logged user can be stopped.
    // 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.ID); 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 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"}' -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"`
    			Cpus int              `json:"cpus"`
    			Ram int               `json:"ram"`
    			Nic vms.NicType       `json:"nic"`
    		}
    		p := new(Parameters)
    		if err := decodeJson(c, &p); err != nil {
    			return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    		}
    		if err := r.vms.EditVM(vm.ID, p.Name, p.Cpus, p.Ram, p.Nic); err != nil {
    			return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    		}
    		return c.JSONPretty(http.StatusOK, jsonMsg("OK"), "    ")
    	})
    }
    
    // Set a VM access for a given user.
    // Requires 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())
    	}
    	if !user.HasCapability(caps.CAP_VM_SET_ACCESS) {
    		return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
    	}
    
    	// Checks the user for which to modify 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())
    	}
    
    	// Deserializes and validates the client's parameters.
    	type Parameters struct {
    		Access caps.Capabilities `json:"access"  validate:"required"`
    	}
    	params := new(Parameters)
    	if err := decodeJson(c, &params); 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 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())
    	}
    	if !user.HasCapability(caps.CAP_VM_SET_ACCESS) {
    		return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
    	}
    
    	// Checks the user for which to modify 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())
    	}
    
    	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 .tar archive.
    // Requires 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
    		tarFile := filepath.Join(tmpDir, "exportdir_"+vm.ID.String()+".tar")
    
    		// Extracts VM's p.Dir directory into tarFile on the host.
    		if err := r.vms.ExportVMFiles(vm, p.Dir, tarFile); 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())
    			}
    		}(tarFile)
    		return c.File(tarFile)
    	})
    }
    
    // Import files into a VM's filesystem, in a specified directory. The file tree is received in a .tar archive.
    // Requires 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 archive (uploadedTarFile).
    		tarFile, err := c.FormFile("file")
    		if err != nil {
    			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
    		}
    
    		src, err := tarFile.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())
    		}
    		uploadedTarFile := filepath.Join(tmpDir, "upload_"+uuid.String()+".tar")
    		dst, err := os.Create(uploadedTarFile)
    		if err != nil {
    			return echo.NewHTTPError(http.StatusInternalServerError, err.Error())
    		}
    		defer dst.Close()
    		defer os.Remove(uploadedTarFile)
    	
    		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, uploadedTarFile, vmDir); err != nil {
    			return echo.NewHTTPError(http.StatusBadRequest, err.Error())
    		}
    	
    		return c.JSONPretty(http.StatusCreated, jsonMsg("OK"), "    ")
    	})
    }
    
    // Helper function that returns a list of VMs that match either:
    // - the logged user has the userCapabilityAny capability.
    // - 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.GetVMs(cond), "    ")
    	} else {
    		// Returns all VMs for which VM access for the logged user matches the specified the VM Access capability (vmAccessCapability).
    		return c.JSONPretty(http.StatusOK, r.vms.GetVMs(
    			func(vm vms.VM) bool {
    				capabilities, exists := vm.Access[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 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 has the XX_ANY capability
    	if user.HasCapability(userCapabilityAny) {
    		return action(c, &vm)
    	} else {
    		// Check if the VM access for the logged user matches the required capability
    		userCaps, exists := vm.Access[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)
    	}
    }