Pārlūkot izejas kodu

Added path edit and user edit

aroz 1 gadu atpakaļ
vecāks
revīzija
2a8ca6be30

+ 126 - 0
mod/fileservers/servers/samba/handlers.go

@@ -374,7 +374,12 @@ func (s *ShareManager) ActivateUserAccount(w http.ResponseWriter, r *http.Reques
 				return
 			}
 		}
+	}
 
+	err = restartSmbd()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
 	}
 
 	utils.SendOK(w)
@@ -447,6 +452,127 @@ func (s *ShareManager) DeactiveUserAccount(w http.ResponseWriter, r *http.Reques
 		return
 	}
 
+	err = restartSmbd()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+
+}
+
+// Handle update to accessible users
+func (s *ShareManager) HandleAccessUserUpdate(w http.ResponseWriter, r *http.Request) {
+	shareName, err := utils.PostPara(r, "name")
+	if err != nil {
+		utils.SendErrorResponse(w, "share name not given")
+		return
+	}
+
+	newUserListJSON, err := utils.PostPara(r, "users")
+	if err != nil {
+		utils.SendErrorResponse(w, "list of new users not given")
+		return
+	}
+
+	//Parse the user list from json string to string slice
+	newUserList := []string{}
+	err = json.Unmarshal([]byte(newUserListJSON), &newUserList)
+	if err != nil {
+		log.Println("[Samba] Parse new user list failed: " + err.Error())
+		utils.SendErrorResponse(w, "failed to parse the new user list")
+		return
+	}
+
+	//read the target share from smb.conf
+	targetShare, err := s.GetShareByName(shareName)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	for _, originalUser := range targetShare.ValidUsers {
+		if !utils.StringInArray(newUserList, originalUser) {
+			//This user is not longer allowed to access this share
+			//remove this user from this share
+			s.RemoveUserFromSambaShare(shareName, originalUser)
+		}
+	}
+
+	for _, newUsername := range newUserList {
+		if !s.UserCanAccessShare(targetShare, newUsername) {
+			err = s.AddUserToSambaShare(targetShare.Name, newUsername)
+			if err != nil {
+				utils.SendErrorResponse(w, err.Error())
+				return
+			}
+		}
+	}
+
+	err = restartSmbd()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	utils.SendOK(w)
+}
+
+// Handle changing path of share
+func (s *ShareManager) HandleSharePathChange(w http.ResponseWriter, r *http.Request) {
+	shareName, err := utils.PostPara(r, "name")
+	if err != nil {
+		utils.SendErrorResponse(w, "share name not given")
+		return
+	}
+
+	newSharePath, err := utils.PostPara(r, "path")
+	if err != nil {
+		utils.SendErrorResponse(w, "list of new users not given")
+		return
+	}
+
+	//Convert path to absolute and check if folder exists
+	newSharePathAbsolute, err := filepath.Abs(newSharePath)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	if !utils.FileExists(newSharePathAbsolute) {
+		utils.SendErrorResponse(w, "target folder not exists")
+		return
+	}
+
+	//Check if path sharing is allowed
+	if isPathInsideImportantFolders(newSharePathAbsolute) {
+		utils.SendErrorResponse(w, "path is or inside protected folders")
+		return
+	}
+
+	//read the target share from smb.conf
+	targetShare, err := s.GetShareByName(shareName)
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Update and save share to smb.conf
+	targetShare.Path = newSharePathAbsolute
+	err = targetShare.SaveToConfig()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
+	//Restart smbd
+	err = restartSmbd()
+	if err != nil {
+		utils.SendErrorResponse(w, err.Error())
+		return
+	}
+
 	utils.SendOK(w)
 
 }

+ 4 - 45
mod/fileservers/servers/samba/samba.go

@@ -4,12 +4,9 @@ import (
 	"bufio"
 	"errors"
 	"fmt"
-	"io"
 	"os"
-	"path/filepath"
 	"runtime"
 	"strings"
-	"time"
 
 	"imuslab.com/arozos/mod/user"
 	"imuslab.com/arozos/mod/utils"
@@ -26,7 +23,6 @@ import (
 
 type ShareManager struct {
 	SambaConfigPath string //Config file for samba, aka smb.conf
-	BackupDir       string //Backup directory for restoring previous config
 	UserHandler     *user.UserHandler
 }
 
@@ -37,6 +33,7 @@ type ShareConfig struct {
 	ReadOnly   bool
 	Browseable bool
 	GuestOk    bool
+	parent     *ShareManager
 }
 
 func NewSambaShareManager(userHandler *user.UserHandler) (*ShareManager, error) {
@@ -50,7 +47,6 @@ func NewSambaShareManager(userHandler *user.UserHandler) (*ShareManager, error)
 	}
 	return &ShareManager{
 		SambaConfigPath: "/etc/samba/smb.conf",
-		BackupDir:       "./backup",
 		UserHandler:     userHandler,
 	}, nil
 }
@@ -73,6 +69,7 @@ func (s *ShareManager) ReadSambaShares() ([]ShareConfig, error) {
 		// Check for section headers
 		if strings.HasPrefix(line, "[") && strings.HasSuffix(line, "]") {
 			if currentShare != nil {
+				currentShare.parent = s
 				shares = append(shares, *currentShare)
 			}
 			currentShare = &ShareConfig{
@@ -107,6 +104,7 @@ func (s *ShareManager) ReadSambaShares() ([]ShareConfig, error) {
 
 	// Add the last share if there is one
 	if currentShare != nil {
+		currentShare.parent = s
 		shares = append(shares, *currentShare)
 	}
 
@@ -136,7 +134,7 @@ func (s *ShareManager) ShareNameExists(sharename string) (bool, error) {
 
 // A basic filter to remove system created smb shares entry in the list
 func (s *ShareManager) FilterSystemCreatedShares(shares []ShareConfig) []ShareConfig {
-	namesToRemove := []string{"global", "homes", "printers", "print$"}
+	namesToRemove := []string{"global", "printers", "print$"}
 	filteredShares := []ShareConfig{}
 	for _, share := range shares {
 		if !utils.StringInArray(namesToRemove, share.Name) {
@@ -300,45 +298,6 @@ func (s *ShareManager) ShareExists(shareName string) (bool, error) {
 	return false, nil
 }
 
-// Backup the current smb.conf to the backup folder
-func (s *ShareManager) BackupSmbConf() error {
-	// Define source and backup directory
-	sourceFile := s.SambaConfigPath
-	backupDir := s.BackupDir
-
-	// Ensure the backup directory exists
-	err := os.MkdirAll(backupDir, 0755)
-	if err != nil {
-		return fmt.Errorf("failed to create backup directory: %v", err)
-	}
-
-	// Create a timestamped backup filename
-	timestamp := time.Now().Format("20060102_150405")
-	backupFile := filepath.Join(backupDir, fmt.Sprintf("%s.smb.conf", timestamp))
-
-	// Open the source file
-	src, err := os.Open(sourceFile)
-	if err != nil {
-		return fmt.Errorf("failed to open source file: %v", err)
-	}
-	defer src.Close()
-
-	// Create the destination file
-	dst, err := os.Create(backupFile)
-	if err != nil {
-		return fmt.Errorf("failed to create backup file: %v", err)
-	}
-	defer dst.Close()
-
-	// Copy the contents of the source file to the backup file
-	_, err = io.Copy(dst, src)
-	if err != nil {
-		return fmt.Errorf("failed to copy file contents: %v", err)
-	}
-
-	return nil
-}
-
 // Add a new user to smb.conf share by name
 func (s *ShareManager) AddUserToSambaShare(shareName, username string) error {
 	targetShare, err := s.GetShareByName(shareName)

+ 51 - 0
mod/fileservers/servers/samba/sambashare.go

@@ -0,0 +1,51 @@
+package samba
+
+import "encoding/json"
+
+/*
+	This script handle the functions related to samba shares
+*/
+
+// Wrapper to save changes to file. Note that the name cannot be changed
+// or otherwise there will be save issue. Use rename instead for name change
+func (s *ShareConfig) SaveToConfig() error {
+	//Deep copy of the current share config
+	newShareConfig := ShareConfig{}
+	originalConfigBytes, _ := json.Marshal(s)
+	err := json.Unmarshal(originalConfigBytes, &newShareConfig)
+	if err != nil {
+		return err
+	}
+
+	//Remove the old one in smb.conf and inject new one
+	shareManager := s.parent
+	err = shareManager.RemoveSambaShareConfig(s.Name)
+	if err != nil {
+		return err
+	}
+
+	return shareManager.CreateNewSambaShare(&newShareConfig)
+}
+
+//Remove this share
+func (s *ShareConfig) Remove() error {
+	return s.parent.RemoveSambaShareConfig(s.Name)
+}
+
+func (s *ShareConfig) Rename(newName string) error {
+	//Deep copy of the current share config
+	newShareConfig := ShareConfig{}
+	originalConfigBytes, _ := json.Marshal(s)
+	err := json.Unmarshal(originalConfigBytes, &newShareConfig)
+	if err != nil {
+		return err
+	}
+
+	//Change the name and create the new one, then remove the old share
+	newShareConfig.Name = newName
+	err = s.parent.CreateNewSambaShare(&newShareConfig)
+	if err != nil {
+		return err
+	}
+	return s.Remove()
+}

+ 27 - 23
network.go

@@ -371,30 +371,34 @@ func FileServerInit() {
 	adminRouter.HandleFunc("/system/storage/ftp/setPort", FTPManager.HandleFTPSetPort)
 	adminRouter.HandleFunc("/system/storage/ftp/passivemode", FTPManager.HandleFTPPassiveModeSettings)
 
-	//Samba Shares
-	//Activate and Deactivate are functions all users can use if admin enabled smbd service
-	router.HandleFunc("/system/storage/samba/activate", func(w http.ResponseWriter, r *http.Request) {
-		if !AuthValidateSecureRequest(w, r, false) {
-			return
-		}
+	//Samba Shares (Optional)
+	if SambaShareManager != nil {
+		//Activate and Deactivate are functions all users can use if admin enabled smbd service
+		router.HandleFunc("/system/storage/samba/activate", func(w http.ResponseWriter, r *http.Request) {
+			if !AuthValidateSecureRequest(w, r, false) {
+				return
+			}
 
-		if !SambaShareManager.IsEnabled() {
-			utils.SendErrorResponse(w, "smbd is not enabled on this server")
-			return
-		}
-		password, _ := utils.PostPara(r, "password")
-		SambaShareManager.ActivateUserAccount(w, r, password)
-	})
-	adminRouter.HandleFunc("/system/storage/samba/deactivate", SambaShareManager.DeactiveUserAccount)
-	adminRouter.HandleFunc("/system/storage/samba/myshare", SambaShareManager.HandleUserSmbStatusList)
-
-	adminRouter.HandleFunc("/system/storage/samba/status", SambaShareManager.SmbdStates)
-	adminRouter.HandleFunc("/system/storage/samba/list", SambaShareManager.ListSambaShares)
-	adminRouter.HandleFunc("/system/storage/samba/add", SambaShareManager.AddSambaShare)
-	adminRouter.HandleFunc("/system/storage/samba/remove", SambaShareManager.DelSambaShare)
-	adminRouter.HandleFunc("/system/storage/samba/addUser", SambaShareManager.NewSambaUser)
-	adminRouter.HandleFunc("/system/storage/samba/delUser", SambaShareManager.DelSambaUser)
-	adminRouter.HandleFunc("/system/storage/samba/listUsers", SambaShareManager.ListSambaUsers)
+			if !SambaShareManager.IsEnabled() {
+				utils.SendErrorResponse(w, "smbd is not enabled on this server")
+				return
+			}
+			password, _ := utils.PostPara(r, "password")
+			SambaShareManager.ActivateUserAccount(w, r, password)
+		})
+		adminRouter.HandleFunc("/system/storage/samba/deactivate", SambaShareManager.DeactiveUserAccount)
+		adminRouter.HandleFunc("/system/storage/samba/myshare", SambaShareManager.HandleUserSmbStatusList)
+
+		adminRouter.HandleFunc("/system/storage/samba/status", SambaShareManager.SmbdStates)
+		adminRouter.HandleFunc("/system/storage/samba/list", SambaShareManager.ListSambaShares)
+		adminRouter.HandleFunc("/system/storage/samba/add", SambaShareManager.AddSambaShare)
+		adminRouter.HandleFunc("/system/storage/samba/editPath", SambaShareManager.HandleSharePathChange)
+		adminRouter.HandleFunc("/system/storage/samba/remove", SambaShareManager.DelSambaShare)
+		adminRouter.HandleFunc("/system/storage/samba/addUser", SambaShareManager.NewSambaUser)
+		adminRouter.HandleFunc("/system/storage/samba/delUser", SambaShareManager.DelSambaUser)
+		adminRouter.HandleFunc("/system/storage/samba/listUsers", SambaShareManager.ListSambaUsers)
+		adminRouter.HandleFunc("/system/storage/samba/updateShareUsers", SambaShareManager.HandleAccessUserUpdate)
+	}
 
 	networkFileServerDaemon = append(networkFileServerDaemon, &fileservers.Server{
 		ID:                "webdav",

+ 7 - 3
web/SystemAO/disk/instr/samba.html

@@ -32,9 +32,7 @@
     </thead>
     <tbody id="userShareList">
         <tr>
-            <td></td>
-            <td></td>
-            <td></td>
+            <td colspan="3"><i class="ui red circle times icon"></i> smbd is not enabled on this server</td>
         </tr>
     </tbody>
 </table>
@@ -56,6 +54,12 @@
                 //Render the user share list
                 $("#userShareList").html(``);
                 data.UserSmbShareList.forEach(smbShare => {
+                    console.log(smbShare);
+                    let visableLogo = "";
+                    if (!smbShare.Browseable){
+                        //This share is hidden from the share tree view
+                        
+                    }
                     $("#userShareList").append(`<tr>
                         <td>${smbShare.Name}</td>
                         <td class="smbaddr" data-clipboard-text="\\\\${window.location.hostname}\\${smbShare.Name}" ><small>\\\\${window.location.hostname}\\${smbShare.Name}</small></td>

+ 97 - 2
web/SystemAO/disk/samba.html

@@ -1,3 +1,10 @@
+<style>
+    .editButton{
+        color: #83b3d2; 
+        cursor: pointer; 
+        float: right;
+    }
+</style>
 <h3>Enable Samba Service</h3>
 <p>Change current systemctl state of smbd (start / stop).</p>
 <div class="ui toggle checkbox">
@@ -112,6 +119,12 @@
             if (data.error){
                 msgbox(data.error, false);
             }else{
+                data.sort(function(a, b) {
+                    if (a.Name < b.Name) return -1;
+                    if (a.Name > b.Name) return 1;
+                    return 0;
+                });
+
                 generateTable(data);
             }
         });
@@ -178,11 +191,12 @@
 
             // Populate the table rows
             data.forEach(item => {
+                let userListHex = encodeURIComponent(JSON.stringify(item.ValidUsers));
                 table += `
                     <tr>
                         <td>${item.Name}</td>
-                        <td>${item.Path}</td>
-                        <td>${item.ValidUsers.join(", ")}</td>
+                        <td>${item.Path} <span class="editButton" onclick="editSharePath('${item.Name}', '${item.Path}');"><i class="edit icon"></i></span></td>
+                        <td>${item.ValidUsers.join(", ")} <span class="shareuserEditBtn editButton" onclick="editSambaUser(this, '${item.Name}');" users="${userListHex}"><i class="edit icon"></i></span></td>
                         <td>${item.ReadOnly?'<i class="ui green check icon"></i>':'<i class="ui red times icon"></i>'}</td>
                         <td>${item.Browseable?'<i class="ui green check icon"></i>':'<i class="ui red times icon"></i>'}</td>
                         <td>${item.GuestOk?'<i class="ui green check icon"></i>':'<i class="ui red times icon"></i>'}</td>
@@ -375,4 +389,85 @@
                 });
             }
         }
+
+        //Inline edit for samba users
+        function editSambaUser(targetDom, shareName){
+            let originalUserList = JSON.parse(decodeURIComponent($(targetDom).attr("users")));
+            let fieldElement = $(targetDom).parent();
+            $(fieldElement).html(`<i class="loading spinner icon"></i> Loading User List`);
+            //Overwrite the DOM element with multi-selection dropdown and save button
+            $.get("../../system/storage/samba/listUsers", function(data){
+                if (data.error == undefined){
+                    $(".shareuserEditBtn").remove();
+                    //Append user selector with default value selected
+                    $(fieldElement).html(`<select multiple="" class="ui search dropdown" id="editValidUserList"></select>`);
+                    data.forEach(function(userinfo){
+                        $("#editValidUserList").append(`<option value="${userinfo.UnixUsername}">${userinfo.UnixUsername}</option>`);
+                    });
+                    $("#editValidUserList").dropdown();
+                    $("#editValidUserList").dropdown("set selected", originalUserList);
+
+                    //Append save and cancel button
+                    $(fieldElement).append(`
+                        <div style="margin-top: 0.6em;"> 
+                            <button class="ui small basic button" onclick="saveSambaUserEdit('${shareName}');"><i class="ui green save icon"></i> Save</button>
+                            <button class="ui small basic button" onclick="initShareListTable();"><i class="ui grey remove icon"></i> Cancel</button>
+                        </div>
+                    `);
+
+                }else{
+                    msgbox("Failed to connect to smbd service", false)
+                }
+            });
+        }
+
+        function saveSambaUserEdit(shareName){
+            //Read selection from #editValidUserList
+            let allowedUsers = $("#editValidUserList").dropdown("get value");
+            if (allowedUsers.length == 0){
+                msgbox("At least one user is required per share", false);
+                return
+            }
+
+            $.ajax({
+                url: "/system/storage/samba/updateShareUsers",
+                method: "POST",
+                data: {
+                    "name": shareName,
+                    "users": JSON.stringify(allowedUsers)
+                },
+                success: function(data){
+                    if (data.error != undefined){
+                        msgbox(data.error, false);
+                    }else{
+                        msgbox("Accessible users updated");
+                    }
+                    //Clear share table
+                    initShareListTable();
+                }
+            });
+        }
+
+        function editSharePath(shareName, originalSharePath){
+            let newpath = prompt("New Share Path", originalSharePath);
+            if (newpath != null && newpath != "") {
+                $.ajax({
+                    url: "/system/storage/samba/editPath",
+                    method: "POST",
+                    data: {
+                        "name": shareName,
+                        "path": newpath
+                    },
+                    success: function(data){
+                        if (data.error != undefined){
+                            msgbox(data.error, false);
+                        }else{
+                            msgbox("Share path updated");
+                        }
+                        //Clear share table
+                        initShareListTable();
+                    }
+                });
+            }
+        }
 </script>