Skip to content
Snippets Groups Projects
Select Git revision
  • 3c31e09557d2da893e974bf681fb9bbabe7e1882
  • live_exam_os_ubuntu default protected
2 results

routerVMs.go

Blame
  • routerVMs.go 29.84 KiB
    package router
    
    import (
        "io"
        "os"
        "net/http"
        "path/filepath"
        "nexus-common/caps"
        "nexus-common/params"
        "nexus-server/vms"
        "nexus-server/users"
        "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.getFilteredVMs(c, caps.CAP_VM_LIST_ANY, caps.CAP_VM_LIST, func(vm *vms.VM) bool {
            return true
        })
    }
    
    // Returns a VM that can be listed based on its UUID.
    // Requires to be the VM's owner, or either capability:
    // User cap: CAP_VM_LIST_ANY: returns the VM.
    // VM access cap: CAP_VM_LIST: returns the VM with this cap for the logged user.
    // curl --cacert ca.pem -X GET https://localhost:1077/vms/62ae8791-c108-4235-a7d6-074e9b6a9017 -H "Authorization: Bearer <AccessToken>"
    func (r *RouterVMs)GetListableVM(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())
        }
    
        // Retrieves the VM based on its UUID.
        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())
        }
    
        // If user has CAP_VM_LIST_ANY capability, returns the VM.
        if user.HasCapability(caps.CAP_VM_LIST_ANY) {
            return c.JSONPretty(http.StatusOK, vm.Serialize(), "    ")
        } else {
            if vm.IsOwner(user.Email) {
                return c.JSONPretty(http.StatusOK, vm.Serialize(), "    ")
            } else {
                capabilities, exists := vm.GetAccess()[user.Email]
                if exists {
                    _, found := capabilities[caps.CAP_VM_LIST]
                    if found {
                        return c.JSONPretty(http.StatusOK, vm.Serialize(), "    ")
                    } else {
                        return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
                    }
                } else {
                    return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
                }
            }
        }
    }
    
    // Returns VMs credentials for VMs that are running and that can be attached to.
    // Requires to be the VM's owner, or either capability:
    // User cap: CAP_VM_ATTACH_ANY: returns VMs credentials for all running VMs.
    // VM access cap: CAP_VM_ATTACH: returns VMs credentials for 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.getFilteredVMCredentials(c, caps.CAP_VM_ATTACH_ANY, caps.CAP_VM_ATTACH, func(vm *vms.VM) bool {
            return vm.IsRunning()
        })
    }
    
    // Returns VM credentials for a VM specified by its UUID.
    // Requires to be the VM's owner, or either capability:
    // User cap: CAP_VM_ATTACH_ANY: returns VM credentials for the VM.
    // VM access cap: CAP_VM_ATTACH: returns VM credentials for the VM with this cap for the logged user.
    // curl --cacert ca.pem -X GET https://localhost:1077/vms/62ae8791-c108-4235-a7d6-074e9b6a9017/attach -H "Authorization: Bearer <AccessToken>"
    func (r *RouterVMs)GetAttachableVM(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())
        }
    
        // Retrieves the VM based on its UUID.
        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())
        }
    
        // If user has CAP_VM_ATTACH_ANY capability, returns the VM.
        if user.HasCapability(caps.CAP_VM_ATTACH_ANY) {
            return c.JSONPretty(http.StatusOK, vm.SerializeCredentials(), "    ")
        } else {
            if vm.IsOwner(user.Email) {
                return c.JSONPretty(http.StatusOK, vm.SerializeCredentials(), "    ")
            } else {
                capabilities, exists := vm.GetAccess()[user.Email]
                if exists {
                    _, found := capabilities[caps.CAP_VM_ATTACH]
                    if found {
                        return c.JSONPretty(http.StatusOK, vm.SerializeCredentials(), "    ")
                    } else {
                        return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
                    }
                } else {
                    return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
                }
            }
        }
    }
    
    // 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.getFilteredVMs(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.getFilteredVMs(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.getFilteredVMs(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.getFilteredVMs(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.getFilteredVMs(c, caps.CAP_VM_EDIT_ANY, caps.CAP_VM_EDIT, func(vm *vms.VM) bool {
            return true
        })
    }
    
    // Returns VMs that can have their access modified (set or deleted).
    // REMARK: running VMs are filtered out, even if they can be modified!
    // Requires to be the VM's owner, or to have VM_SET_ACCESS_ANY capability, or to have 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)GetModifiableVMAccessVMs(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())
        }
    
        // Checks whether the logged user is allowed to modify (add/remove) a VM's VM access.
        isModifyVMAccessAllowed := func(user *users.User, vm *vms.VM) bool {
            // If user has VM_SET_ACCESS_ANY, modify is allowed.
            if user.HasCapability(caps.CAP_VM_SET_ACCESS_ANY) {
                return true
            } else {
                // If user is the VM's owner, modify is allowed.
                if vm.IsOwner(user.Email) {
                    return true
                } else {
                    // If user has VM_SET_ACCESS and VM's VM access is present for the same user,
                    // modify is allowed.
                    if user.HasCapability(caps.CAP_VM_SET_ACCESS) {
                        capabilities, exists := vm.GetAccess()[user.Email]
                        if exists {
                            _, found := capabilities[caps.CAP_VM_SET_ACCESS]
                            return found
                        } else {
                            return false
                        }
                    } else {
                        return false
                    }
                }
            }
        }
    
        filterFunc := func(vm *vms.VM) bool {
            return isModifyVMAccessAllowed(user, vm)
        }
    
        return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMs(filterFunc), "    ")
    }
    
    // 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.getFilteredVMs(c, caps.CAP_VM_READFS_ANY, caps.CAP_VM_READFS, func(vm *vms.VM) bool {
            return !vm.IsRunning()
        })
    }
    
    // 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.getFilteredVMs(c, caps.CAP_VM_WRITEFS_ANY, caps.CAP_VM_WRITEFS, func(vm *vms.VM) bool {
            return !vm.IsRunning()
        })
    }
    
    // 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.
        p := new(params.VMCreate)
        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.Serialize(), "    ")
    }
    
    // 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)DeleteVM(c echo.Context) error {
        return r.applyOnFilteredVMs(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.applyOnFilteredVMs(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"), "    ")
        })
    }
    
    // Starts a VM based on its ID using the specified credentials (port and password).
    // 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/startwithcreds -H "Authorization: Bearer <AccessToken>"
    func (r *RouterVMs)StartVMWithCreds(c echo.Context) error {
        return r.applyOnFilteredVMs(c, caps.CAP_VM_START_ANY, caps.CAP_VM_START, func(c echo.Context, vm *vms.VM) error {
            // Deserializes and validates client's parameters.
            p := new(params.VMStartWithCreds)
            if err := decodeJson(c, &p); err != nil {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
    
            if err := r.vms.StartVMWithCreds(vm.GetID(), p.Port, true, p.Pwd); 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.applyOnFilteredVMs(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.applyOnFilteredVMs(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.applyOnFilteredVMs(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)EditVM(c echo.Context) error {
        return r.applyOnFilteredVMs(c, caps.CAP_VM_EDIT_ANY, caps.CAP_VM_EDIT, func(c echo.Context, vm *vms.VM) error {
            // Deserializes and validates client's parameters.
            // Given these parameters are optional, we can't use a validator on them.
            // Validation is performed in vm.EditVM() instead.
            p := new(params.VMEdit)
            if err := decodeJson(c, &p); err != nil {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
            if err := r.vms.EditVM(vm.GetID(), p); err != nil {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
            return c.JSONPretty(http.StatusOK, vm.Serialize(), "    ")
        })
    }
    
    // Set a VM access for a given user.
    // Requires to be the VM's owner, or to have VM_SET_ACCESS_ANY capability, or to have 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())
        }
    
        // Deserializes and validates client's parameters.
        p := new(params.VMAddAccess)
        if err := decodeJson(c, &p); err != nil {
            return echo.NewHTTPError(http.StatusBadRequest, err.Error())
        }
        if err := validator.New(validator.WithRequiredStructEnabled()).Struct(p); err != nil {
            return echo.NewHTTPError(http.StatusBadRequest, err.Error())
        }
    
        if err = r.vms.SetVMAccess(vmID, user, email, p.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())
        }
    
        // Purposedly does not check that the user to remove the VM access for actually
        // exists as it might have been deleted.
        email := c.Param("email")
    
        if err = r.vms.DeleteVMAccess(vmID, user, 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.applyOnFilteredVMs(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.
            p := new(params.VMExportDir)
            if err := decodeJson(c, &p); err != nil {
                return echo.NewHTTPError(http.StatusBadRequest, err.Error())
            }
    
            // Creates a unique tar filename.
            tarGzFile := filepath.Join(conf.Core.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.applyOnFilteredVMs(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()
    
            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(conf.Core.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 if either condition is true:
    // - 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.
    // Additionally, VMs for which the cond function returns false are filtered out.
    func (r *RouterVMs)getFilteredVMs(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 user has the XX_ANY capability, 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 user
            // - for which the 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 {
                        _, found := capabilities[vmAccessCapability]
                        return found && cond(vm)
                    } else {
                        return false
                    }
                }
            }), "    ")
        }
    }
    
    // Helper function that returns a list of serialized VM credentials if either condition is true:
    // - 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.
    // Additionally, VMs for which the cond function returns false are filtered out.
    func (r *RouterVMs)getFilteredVMCredentials(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 user has the XX_ANY capability, returns all VMs.
        if user.HasCapability(userCapabilityAny) {
            // Returns all VMs that pass the condition.
            return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMCredentials(cond), "    ")
        } else {
            // Returns all VMs:
            // - owned by the user
            // - for which the user has the specified capability (vmAccessCapability) in the VM's access
            return c.JSONPretty(http.StatusOK, r.vms.GetNetworkSerializedVMCredentials(func(vm *vms.VM) bool {
                if vm.IsOwner(user.Email) {
                    return cond(vm)
                } else {
                    capabilities, exists := vm.GetAccess()[user.Email]
                    if exists {
                        _, found := capabilities[vmAccessCapability]
                        return found && cond(vm)
                    } else {
                        return false
                    }
                }
            }), "    ")
        }
    }
    
    // Helper function that executes an action on a VM if either condition is true:
    // - 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)applyOnFilteredVMs(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())
        }
    
        // If user is the VM's owner, executes the action.
        if vm.IsOwner(user.Email) {
            return action(c, vm)
        // If user has the XX_ANY capability, executes the action.
        } else if user.HasCapability(userCapabilityAny) {
            return action(c, vm)
        // Finally, if VM access for the logged user matches the required capability, executes the action.
        } else {
            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)
        }
    }