Просмотр исходного кода

Backup before trting user inject

aroz 1 год назад
Родитель
Сommit
d717f056b8

+ 89 - 5
mod/fileservers/servers/samba/handlers.go

@@ -2,6 +2,7 @@ package samba
 
 import (
 	"encoding/json"
+	"log"
 	"net/http"
 	"path/filepath"
 	"strings"
@@ -224,14 +225,13 @@ func (s *ShareManager) DelSambaShare(w http.ResponseWriter, r *http.Request) {
 
 // Add a new samba user
 func (s *ShareManager) NewSambaUser(w http.ResponseWriter, r *http.Request) {
-	//TODO: Replace the GetPara to Post
-	username, err := utils.GetPara(r, "username")
+	username, err := utils.PostPara(r, "username")
 	if err != nil {
 		utils.SendErrorResponse(w, "username not given")
 		return
 	}
 
-	password, err := utils.GetPara(r, "password")
+	password, err := utils.PostPara(r, "password")
 	if err != nil {
 		utils.SendErrorResponse(w, "password not set")
 		return
@@ -246,9 +246,9 @@ func (s *ShareManager) NewSambaUser(w http.ResponseWriter, r *http.Request) {
 	utils.SendOK(w)
 }
 
-// Remove a samba user
+// Remove a samba user, check for admin before calling
 func (s *ShareManager) DelSambaUser(w http.ResponseWriter, r *http.Request) {
-	username, err := utils.GetPara(r, "username")
+	username, err := utils.PostPara(r, "username")
 	if err != nil {
 		utils.SendErrorResponse(w, "username not given")
 		return
@@ -275,3 +275,87 @@ func (s *ShareManager) ListSambaUsers(w http.ResponseWriter, r *http.Request) {
 	js, _ := json.Marshal(userInfo)
 	utils.SendJSONResponse(w, string(js))
 }
+
+// Activate a user account from arozos into samba user
+func (s *ShareManager) ActivateUserAccount(w http.ResponseWriter, r *http.Request, username string, password string) {
+	//Register this user to samba if not exists
+	sambaUserExists, err := s.SambaUserExists(username)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	userInfo, _ := s.UserHandler.GetUserInfoFromRequest(w, r)
+	if !sambaUserExists {
+		//This user account not activated yet. Activate it
+		err = s.AddSambaUser(userInfo.Username, password)
+		if err != nil {
+			utils.SendErrorResponse(w, err.Error())
+			return
+		}
+	}
+
+	//Create the user root share folders
+	for _, fsh := range userInfo.GetAllAccessibleFileSystemHandler() {
+		if fsh.IsLocalDrive() {
+			//Samba can only work with drives locally hosted on this server
+			fshID := fsh.UUID
+			fshSharePath := fsh.Path
+			if fsh.RequierUserIsolation() {
+				//User seperated storage. Only mount the user one
+				fshID = fsh.UUID + "_" + userInfo.Username
+				fshSharePath = filepath.Join(fsh.Path, "/users/", userInfo.Username+"/")
+			}
+
+			fshID = sanitizeShareName(fshID)
+
+			//Check if the share already exists
+			shareExists, err := s.ShareExists(fshID)
+			if err != nil {
+				continue
+			}
+
+			if !shareExists {
+				//Try to create the share
+				fshShareAbsolutePath, err := filepath.Abs(fshSharePath)
+				if err != nil {
+					log.Println("[Samba] Unable to generate share config for path: " + fshSharePath)
+					continue
+				}
+
+				//Check if that folder exists
+				if utils.FileExists(fshShareAbsolutePath) {
+					//Folder not exists. Continue
+					log.Println("[Samba] Path not exists for file system handler: " + fshSharePath)
+					continue
+				}
+
+				//Ok! Create the share with this username
+				err = s.CreateNewSambaShare(&ShareConfig{
+					Name:       fshID,
+					Path:       fshShareAbsolutePath,
+					ValidUsers: []string{userInfo.Username},
+					ReadOnly:   false,
+					Browseable: true,
+					GuestOk:    false,
+				})
+
+				if err != nil {
+					log.Println("[Samba] Failed to create share: " + err.Error())
+					utils.SendErrorResponse(w, err.Error())
+					return
+				}
+			} else {
+				//Share exists. Add this user to such share
+				err = s.AddUserToSambaShare(fshID, userInfo.Username)
+				if err != nil {
+					log.Println("[Samba] Failed to add user " + userInfo.Username + " to share " + fshID + ": " + err.Error())
+					utils.SendErrorResponse(w, err.Error())
+					return
+				}
+			}
+
+		}
+	}
+	utils.SendOK(w)
+}

+ 14 - 0
mod/fileservers/servers/samba/helpers.go

@@ -5,6 +5,7 @@ import (
 	"os/exec"
 	"path/filepath"
 	"strings"
+	"unicode"
 )
 
 // convertShareConfigToString converts a ShareConfig to its string representation for smb.conf
@@ -90,3 +91,16 @@ func isPathInsideImportantFolders(path string) bool {
 	}
 	return false
 }
+
+// Clean and make sure the share name is valid
+func sanitizeShareName(input string) string {
+	var result strings.Builder
+	for _, char := range input {
+		if unicode.IsLetter(char) || unicode.IsDigit(char) {
+			result.WriteRune(char)
+		} else if unicode.IsSpace(char) {
+			result.WriteRune(' ')
+		}
+	}
+	return result.String()
+}

+ 79 - 0
mod/fileservers/servers/samba/samba.go

@@ -11,6 +11,7 @@ import (
 	"strings"
 	"time"
 
+	"imuslab.com/arozos/mod/user"
 	"imuslab.com/arozos/mod/utils"
 )
 
@@ -26,6 +27,7 @@ import (
 type ShareManager struct {
 	SambaConfigPath string //Config file for samba, aka smb.conf
 	BackupDir       string //Backup directory for restoring previous config
+	UserHandler     *user.UserHandler
 }
 
 type ShareConfig struct {
@@ -320,3 +322,80 @@ func (s *ShareManager) BackupSmbConf() error {
 
 	return nil
 }
+
+// Add a new user to smb.conf share by name
+func (s *ShareManager) AddUserToSambaShare(shareName, username string) error {
+	// Open the smb.conf file for reading
+	file, err := os.Open(s.SambaConfigPath)
+	if err != nil {
+		return fmt.Errorf("failed to open smb.conf: %v", err)
+	}
+	defer file.Close()
+
+	var lines []string
+	var insideShare bool
+	var shareExists bool
+	var userAdded bool
+
+	scanner := bufio.NewScanner(file)
+	for scanner.Scan() {
+		line := scanner.Text()
+		lines = append(lines, line)
+
+		// Check if we are inside the specified share section
+		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
+			if insideShare && shareExists && !userAdded {
+				// Add the username to the valid users list
+				lines = append(lines, "   valid users = @"+username)
+				userAdded = true
+			}
+			insideShare = false
+		}
+
+		if strings.TrimSpace(line) == fmt.Sprintf("[%s]", shareName) {
+			insideShare = true
+			shareExists = true
+		}
+
+		if insideShare && strings.HasPrefix(strings.TrimSpace(line), "valid users =") {
+			// Check if the username already exists in the valid users list
+			validUsersLine := strings.TrimSpace(line)
+			if !strings.Contains(validUsersLine, username) {
+				lines[len(lines)-1] = validUsersLine + ", @" + username
+				userAdded = true
+			}
+		}
+	}
+
+	if err := scanner.Err(); err != nil {
+		return fmt.Errorf("error reading smb.conf: %v", err)
+	}
+
+	if !shareExists {
+		return fmt.Errorf("share [%s] not found in smb.conf", shareName)
+	}
+
+	if !userAdded {
+		// If no valid users line was found, add the username to the share
+		lines = append(lines, fmt.Sprintf("[%s]", shareName))
+		lines = append(lines, "   valid users = @"+username)
+	}
+
+	// Write the updated configuration back to the smb.conf file
+	outputFile, err := os.Create(s.SambaConfigPath)
+	if err != nil {
+		return fmt.Errorf("failed to open smb.conf for writing: %v", err)
+	}
+	defer outputFile.Close()
+
+	writer := bufio.NewWriter(outputFile)
+	for _, line := range lines {
+		_, err := writer.WriteString(line + "\n")
+		if err != nil {
+			return fmt.Errorf("error writing to smb.conf: %v", err)
+		}
+	}
+	writer.Flush()
+
+	return nil
+}

+ 5 - 0
mod/filesystem/filesystem.go

@@ -463,6 +463,11 @@ func (fsh *FileSystemHandler) ReloadFileSystelAbstraction() error {
 	return nil
 }
 
+// Check if this file system require user isolation (aka Hierarchy == user)
+func (fsh *FileSystemHandler) RequierUserIsolation() bool {
+	return fsh.Hierarchy == "user"
+}
+
 // Close an openeded File System
 func (fsh *FileSystemHandler) Close() {
 	//Set the close flag to true so others function wont access it

+ 67 - 0
user.go

@@ -7,7 +7,12 @@ package main
 */
 
 import (
+	"encoding/base64"
 	"encoding/json"
+	"image"
+	"image/gif"
+	"image/jpeg"
+	"image/png"
 	"net/http"
 	"strconv"
 	"strings"
@@ -53,6 +58,11 @@ func UserSystemInit() {
 		authAgent.HandleCheckAuth(w, r, user_getInterfaceInfo)
 	})
 
+	//API for loading other users thumbnail as image file
+	http.HandleFunc("/system/users/profilepic", func(w http.ResponseWriter, r *http.Request) {
+		authAgent.HandleCheckAuth(w, r, user_getProfilePic)
+	})
+
 	//Register setting interface for module configuration
 	registerSetting(settingModule{
 		Name:         "My Account",
@@ -299,6 +309,63 @@ func user_getInterfaceInfo(w http.ResponseWriter, r *http.Request) {
 	utils.SendJSONResponse(w, string(jsonString))
 }
 
+// return the user profile picture as image file
+func user_getProfilePic(w http.ResponseWriter, r *http.Request) {
+	thisUsername, err := authAgent.GetUserName(w, r)
+	if err != nil {
+		utils.SendErrorResponse(w, "User not logged in")
+		return
+	}
+
+	targetUsername, err := utils.GetPara(r, "user")
+	if err != nil {
+		targetUsername = thisUsername
+	}
+
+	base64Image := getUserIcon(targetUsername)
+	if base64Image == "" && utils.FileExists("./web/img/system/close.png") {
+		//There are no profile image for this user
+		http.ServeFile(w, r, "./web/img/system/close.png")
+		return
+	}
+
+	// Remove the data:image/...;base64, part if it exists
+	if commaIndex := strings.Index(base64Image, ","); commaIndex != -1 {
+		base64Image = base64Image[commaIndex+1:]
+	}
+
+	imgBytes, err := base64.StdEncoding.DecodeString(base64Image)
+	if err != nil {
+		http.Error(w, "Failed to decode base64 string", http.StatusInternalServerError)
+		return
+	}
+
+	img, format, err := image.Decode(strings.NewReader(string(imgBytes)))
+	if err != nil {
+		http.Error(w, "Failed to decode image", http.StatusInternalServerError)
+		return
+	}
+
+	switch format {
+	case "jpeg":
+		w.Header().Set("Content-Type", "image/jpeg")
+		err = jpeg.Encode(w, img, nil)
+	case "png":
+		w.Header().Set("Content-Type", "image/png")
+		err = png.Encode(w, img)
+	case "gif":
+		w.Header().Set("Content-Type", "image/gif")
+		err = gif.Encode(w, img, nil)
+	default:
+		http.Error(w, "Unsupported image format", http.StatusInternalServerError)
+		return
+	}
+
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+	}
+}
+
 func user_handleUserInfo(w http.ResponseWriter, r *http.Request) {
 	username, err := authAgent.GetUserName(w, r)
 	if err != nil {

+ 129 - 1
web/SystemAO/disk/samba.html

@@ -14,6 +14,7 @@
     <br>
 </div>
 
+<!-- Create new Samba Share -->
 <div class="ui divider"></div>
 <h3><i class="ui green circle add icon"></i> Add Samba Share</h3>
 <p>Create a new SMB share folder from local disk</p>
@@ -31,6 +32,7 @@
         <label for="validUsers">Valid Users</label>
         <select multiple="" class="ui search dropdown" id="validUsers">
         </select>
+        <button onclick="event.preventDefault(); initSambaUserList();" class="ui mini basic button" style="margin-top: 0.4em;"><i class="ui green refresh icon"></i> Refresh User List</button>
     </div>
     <div class="field">
         <div class="ui checkbox">
@@ -55,8 +57,33 @@
     </div>
     <button type="button" class="ui small basic button" onclick="newSambaShare(); event.preventDefault();"><i class="ui green circle add icon"></i> Create Share</button>
 </form>
+<!-- Create new Samba user -->
+<div class="ui divider"></div>
+<p>Current list of users registered in Samba database</p>
+<div id="userTableContainer"></div>
+
+<h3><i class="ui green user plus icon"></i> Add Samba User</h3>
+<p>Create Samba user for sharing<br>
+<small>Samba user is not ArozOS user. Creating a Samba user will also create a unix user with login function disabled</small></p>
+<div class="ui container">
+    <form class="ui form" id="userForm">
+        <div class="field">
+            <label for="username">Username</label>
+            <input type="text" id="smbuser_username" placeholder="Enter username" required>
+        </div>
+        <div class="field">
+            <label for="password">Password</label>
+            <input type="password" id="smbuser_password" placeholder="Enter password" required>
+        </div>
+        <div class="field">
+            <label for="confirmPassword">Confirm Password</label>
+            <input type="password" id="smbuser_confirmPassword" placeholder="Confirm password" required>
+        </div>
+        <button type="button" class="ui basic button" onclick="createNewSambaUser()"><i class="ui checkmark green icon"></i> Confirm</button>
+    </form>
 </div>
 
+
 <script>
     $("#validUsers").dropdown();
     $("#shareForm").find("checkbox").checkbox();
@@ -66,6 +93,7 @@
         $.get("../../system/storage/samba/listUsers", function(data){
             if (data.error == undefined){
                 $("#validUsers").html("");
+                renderUserTable(data);
                 if (data.length == 0){
                     return;
                 }
@@ -73,7 +101,7 @@
                     $("#validUsers").append(`<option value="${userinfo.UnixUsername}">${userinfo.UnixUsername}</option>`);
                 })
             }
-        })
+        });
     }
     initSambaUserList();
 
@@ -245,4 +273,104 @@
                 })
             }
         }
+
+
+        //Render current list of users in samba
+        function renderUserTable(data) {
+            // Start the table
+            let table = `
+                <table class="ui celled basic small table">
+                    <thead>
+                        <tr>
+                            <th>Unix Username</th>
+                            <th>Domain</th>
+                            <th>Remove</th>
+                        </tr>
+                    </thead>
+                    <tbody>
+            `;
+
+            // Populate the table rows
+            data.forEach(item => {
+                table += `
+                    <tr>
+                        <td><img class="ui avatar image" src="/system/users/profilepic?user=${item.UnixUsername}"> ${item.UnixUsername}</td>
+                        <td>${item.Domain}</td>
+                        <td><button class="ui basic small red button" onclick="removeSambaUser('${item.UnixUsername}');"><i class="ui red trash icon"></i> Remove Samba User</button></td>
+                    </tr>
+                `;
+            });
+
+            if (data.length == 0){
+                table += `<tr><td colspan="3"><i class="ui green circle check icon"></i> No registered users in Samba database</td></tr>`;
+            }
+
+            // Close the table
+            table += `
+                    </tbody>
+                </table>
+            `;
+
+            // Insert the table into the div
+            $('#userTableContainer').html(table);
+        }
+
+
+        //Create a new samba user
+        function createNewSambaUser(){
+            // Get values from the form
+            const username = $('#smbuser_username').val();
+            const password = $('#smbuser_password').val();
+            const confirmPassword = $('#smbuser_confirmPassword').val();
+
+            // Check if passwords match
+            if (password !== confirmPassword) {
+                msgbox("Confirm password does not match!", false);
+            }
+
+            $.ajax({
+                url: "/system/storage/samba/addUser",
+                method: "POST",
+                data: {
+                    "username": username,
+                    "password": password
+                },
+                success: function(data){
+                    if (data.error != undefined){
+                        msgbox(data.error, false, 5000);
+                    }else{
+                        msgbox("New Samba user created");
+                        $("#smbuser_username").val("");
+                        $("#smbuser_password").val("");
+                        $("#smbuser_confirmPassword").val("");
+                    }
+                    //Update the samba user list in share
+                    initSambaUserList();
+                }
+            })
+        }
+
+        //Remove a samba user given the username
+        function removeSambaUser(targetUsername){
+            if (confirm("Confirm remove samba user \"" + targetUsername + "\" ?")){
+                $.ajax({
+                    url: "/system/storage/samba/delUser",
+                    method: "POST",
+                    data: {
+                        "username": targetUsername
+                    },
+                    success: function(data){
+                        if (data.error != undefined){
+                            msgbox(data.error, false, 5000);
+                        }else{
+                            msgbox("Samba user removed");
+                        }
+
+                        //Update the samba user list in share
+                        initSambaUserList();
+                    }
+                });
+            }
+            
+        }
 </script>