From bb4bda9c161303604bd575b40f00b90bc1895498 Mon Sep 17 00:00:00 2001
From: Florent Gluck <florent.gluck@hesge.ch>
Date: Thu, 28 Mar 2024 20:35:12 +0100
Subject: [PATCH] Added a route to retrieve a single VM

---
 docs/README_server.md          |  2 +-
 src/client/nexush/nexush.go    |  1 +
 src/server/router/router.go    |  1 +
 src/server/router/routerVMs.go | 44 ++++++++++++++++++++++++++++++++++
 src/server/vms/vm.go           |  4 ++--
 5 files changed, 49 insertions(+), 3 deletions(-)

diff --git a/docs/README_server.md b/docs/README_server.md
index 16340f6..814f0e5 100644
--- a/docs/README_server.md
+++ b/docs/README_server.md
@@ -338,6 +338,7 @@ These capabilities are called "VM access capabilities":
 | Route                    | Description                                    | Method | Parameters | Req. user cap.   | Op. | Req. VM access cap. |
 |---                       |---                                             |---     |---         |---               |---  |---                  |
 | `/vms`                   | returns VMs that can be listed                 | GET    |            | `VM_LIST_ANY`    | OR  | `VM_LIST`           |
+| `/vms/{id}`              | returns a VM                                   | GET    |            | `VM_LIST_ANY`    | OR  | `VM_LIST`           |
 | `/vms/start`             | returns VMs that can be started                | GET    |            | `VM_START_ANY`   | OR  | `VM_START`          |
 | `/vms/attach`            | returns VMs that can be attached to            | GET    |            | `VM_LIST_ANY`    | OR  | `VM_LIST`           |
 | `/vms/stop`              | returns VMs that can be killed/shutdown        | GET    |            | `VM_STOP_ANY`    | OR  | `VM_STOP`           |
@@ -354,7 +355,6 @@ These capabilities are called "VM access capabilities":
 | `/vms/{id}`                | delete a VM                  | DELETE |                                | `VM_DESTROY_ANY` | OR  | `VM_DESTROY`        |
 | `/vms/{id}`                | edit a VM                    | PUT    | name,cpus,ram,nic,usb          | `VM_EDIT_ANY`    | OR  | `VM_EDIT`           |
 | `/vms/{id}/start`          | start a VM                   | PUT    |                                | `VM_START_ANY`   | OR  | `VM_START`          |
-| `/vms/{id}/start`          | start a VM                   | PUT    |                                | `VM_START_ANY`   | OR  | `VM_START`          |
 | `/vms/{id}/startwithcreds` | start a VM with credentials  | PUT    | port,pwd                       | `VM_START_ANY`   | OR  | `VM_START`          |
 | `/vms/{id}/stop`           | kill a VM                    | PUT    |                                | `VM_STOP_ANY`    | OR  | `VM_STOP`           |
 | `/vms/{id}/reboot`         | reboot a VM                  | PUT    |                                | `VM_REBOOT_ANY`  | OR  | `VM_REBOOT`         |
diff --git a/src/client/nexush/nexush.go b/src/client/nexush/nexush.go
index 6b92a79..5787b35 100644
--- a/src/client/nexush/nexush.go
+++ b/src/client/nexush/nexush.go
@@ -63,6 +63,7 @@ var cmdList = []cmd.Command {
     &cmdVM.ImportDir{"vmimportdir"},
     &cmdVM.Stop{"vmkill"},
     &cmdVM.List{"vmlist"},
+    &cmdVM.ListSingle{"vmlistsingle"},
     &cmdVM.Reboot{"vmreboot"},
     &cmdVM.Shutdown{"vmshutdown"},
     &cmdVM.Start{"vmstart"},
diff --git a/src/server/router/router.go b/src/server/router/router.go
index d4f471d..e35b910 100644
--- a/src/server/router/router.go
+++ b/src/server/router/router.go
@@ -71,6 +71,7 @@ func (router *Router)Start(port int) {
     // VM management.
     vmsGroup := router.echo.Group("/vms")
     vmsGroup.Use(middleware.JWTWithConfig(auth.GetTokenAccess()))
+    vmsGroup.GET("/:id", router.vms.GetListableVM)
     vmsGroup.GET("", router.vms.GetListableVMs)
     vmsGroup.GET("/attach", router.vms.GetAttachableVMs)
     vmsGroup.GET("/del", router.vms.GetDeletableVMs)
diff --git a/src/server/router/routerVMs.go b/src/server/router/routerVMs.go
index 34cca62..d687db8 100644
--- a/src/server/router/routerVMs.go
+++ b/src/server/router/routerVMs.go
@@ -40,6 +40,50 @@ func (r *RouterVMs)GetListableVMs(c echo.Context) error {
     })
 }
 
+// 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/vm/62ae8791-c108-4235-a7d6-074e9b6a9017 -H "Authorization: Bearer <AccessToken>"
+func (r *RouterVMs)GetListableVM(c echo.Context) 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())
+    }
+
+    // Retrieves logged user from context.
+    user, err := getLoggedUser(r.users, c)
+    if err != nil {
+        return echo.NewHTTPError(http.StatusUnauthorized, 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.SerializeToNetwork(), "    ")
+    } else {
+        if vm.IsOwner(user.Email) {
+            return c.JSONPretty(http.StatusOK, vm.SerializeToNetwork(), "    ")
+        } else {
+            capabilities, exists := vm.GetAccess()[user.Email]
+            if exists {
+                _, found := capabilities[caps.CAP_VM_LIST]
+                if found {
+                    return c.JSONPretty(http.StatusOK, vm.SerializeToNetwork(), "    ")
+                } else {
+                    return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
+                }
+            } else {
+                return echo.NewHTTPError(http.StatusUnauthorized, msgInsufficientCaps)
+            }
+        }
+    }
+}
+
 // 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.
diff --git a/src/server/vms/vm.go b/src/server/vms/vm.go
index 95d9cd7..ea8662f 100644
--- a/src/server/vms/vm.go
+++ b/src/server/vms/vm.go
@@ -84,7 +84,7 @@ func NewVM(creatorEmail string, name string, cpus, ram int, nic vmc.NicType, usb
     return vm, nil
 }
 
-// Returns a serialized version (to be written to disk) of the template.
+// Returns a serialized version (to be written to disk) of the VM.
 // Concurrency: safe
 func (vm *VM)SerializeToDisk() vmc.VMDiskSerialized {
     return vmc.VMDiskSerialized {
@@ -100,7 +100,7 @@ func (vm *VM)SerializeToDisk() vmc.VMDiskSerialized {
     }
 }
 
-// Returns a serialized version (to be sent to the network) of the template.
+// Returns a serialized version (to be sent to the network) of the VM.
 // Concurrency: safe
 func (vm *VM)SerializeToNetwork() vmc.VMNetworkSerialized {
     return vmc.VMNetworkSerialized {
-- 
GitLab