From 58d0f8aec79a3c947f970459d5a3fd7ecd6d2c05 Mon Sep 17 00:00:00 2001
From: Theo S Schult <theo.sschult@gmail.com>
Date: Thu, 6 Feb 2025 21:47:11 -0300
Subject: [PATCH] Implement mod subcommand inteface

---
 .gitignore         |   1 +
 cmd/user/create.go |   6 ++
 cmd/user/delete.go |   2 +-
 cmd/user/mod.go    | 145 ++++++++++++++++++++++++++++++++++++++++++++-
 model/user.go      |   8 ++-
 5 files changed, 157 insertions(+), 5 deletions(-)

diff --git a/.gitignore b/.gitignore
index 2d0b03e..97caac8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
 useradm
+build.sh
diff --git a/cmd/user/create.go b/cmd/user/create.go
index 455b98e..7022dee 100644
--- a/cmd/user/create.go
+++ b/cmd/user/create.go
@@ -27,6 +27,7 @@ func init() {
 	CreateCmd.Flags().StringP("shell", "s", "/bin/bash", "Full path to shell ")
 	CreateCmd.Flags().StringP("homedir", "d", "", "Home directory path (/home/group/login if empty)")
 	CreateCmd.Flags().StringP("password", "p", "", "User password       (auto-generated if empty)")
+	CreateCmd.Flags().StringP("status", "a", "Free", "User status      (Blocked/Free)")
 
 	// Required Flags
 	CreateCmd.MarkFlagRequired("name")
@@ -77,6 +78,10 @@ func createUserFunc(cmd *cobra.Command, args []string) error {
 	if err != nil {
 		return err
 	}
+	userStatus, err := cmd.Flags().GetString("status")
+	if err != nil {
+		return err
+	}
 
 	user := model.User{
 		Name:     userName,
@@ -88,6 +93,7 @@ func createUserFunc(cmd *cobra.Command, args []string) error {
 		Shell:    userShell,
 		Homedir:  userHomedir,
 		Password: ifThenElse(userPassword != "", "[set]", "[auto-generate]"),
+        Status:   userStatus,
 	}
 
 	fmt.Println(user.ToString())
diff --git a/cmd/user/delete.go b/cmd/user/delete.go
index a55844e..2777308 100644
--- a/cmd/user/delete.go
+++ b/cmd/user/delete.go
@@ -4,5 +4,5 @@ import "github.com/spf13/cobra"
 
 var DeleteCmd = &cobra.Command{
 	Use:   "delete",
-	Short: "Deleta a user",
+	Short: "Delete a user",
 }
diff --git a/cmd/user/mod.go b/cmd/user/mod.go
index 66b2a59..5dc5b03 100644
--- a/cmd/user/mod.go
+++ b/cmd/user/mod.go
@@ -1,8 +1,151 @@
 package user
 
-import "github.com/spf13/cobra"
+import (
+    "os"
+    "fmt"
+    "bufio"
+    "errors"
+    "strings"
+    "crypto/rand"
+
+    "github.com/spf13/cobra"
+    "gitlab.c3sl.ufpr.br/tss24/useradm/model"
+)
 
 var ModCmd = &cobra.Command{
 	Use:   "mod",
 	Short: "Modify user information",
+    RunE: modifyUserFunc,
+}
+
+func init() {
+    ModCmd.Flags().StringP("grr", "r", "", "New user GRR")
+    ModCmd.Flags().StringP("shell", "s", "", "New user shell")
+    ModCmd.Flags().StringP("login", "l", "", "User login name")
+    ModCmd.Flags().StringP("homedir", "d", "", "New user home")
+    ModCmd.Flags().BoolP  ("unblock", "u", false, "Unblocks the user")
+    ModCmd.Flags().StringP("group", "g", "", "New user initial group")
+    ModCmd.Flags().StringP("path", "w", "", "Create Webdir, path/login")
+    ModCmd.Flags().StringP("name", "n", "", "New user full name (use quotes for spaces)")
+    ModCmd.Flags().StringP("password", "p", "", "New password in plain text (use \"auto\" to autogenerate)")
+    ModCmd.Flags().BoolP  ("block", "b", false, "Block the user (changes the password and sets their shell to /bin/false)")
+
+    ModCmd.MarkFlagRequired("login")
+
+    ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt")
+}
+
+func modifyUserFunc(cmd *cobra.Command, args []string) error {
+    confirm, err := cmd.Flags().GetBool("confirm")
+    if err != nil {
+            return err
+    }
+    block, err := cmd.Flags().GetBool("block")
+    if err != nil {
+            return err
+    }
+    unblock, err := cmd.Flags().GetBool("unblock")
+    if err != nil {
+            return err
+    }
+    userNameNew, err := cmd.Flags().GetString("name")
+    if err != nil {
+            return err
+    }
+    userGroupNew, err := cmd.Flags().GetString("group")
+    if err != nil {
+            return err
+    }
+    userGRRNew, err := cmd.Flags().GetString("grr")
+    if err != nil {
+            return err
+    }
+    userPathNew, err := cmd.Flags().GetString("path")
+    if err != nil {
+            return err
+    }
+    userLogin, err := cmd.Flags().GetString("login")
+    if err != nil {
+            return err
+    }
+    userShellNew, err := cmd.Flags().GetString("shell")
+    if err != nil {
+            return err
+    }
+    userHomedirNew, err := cmd.Flags().GetString("homedir")
+    if err != nil {
+            return err
+    }
+    userPasswordNew, err := cmd.Flags().GetString("password")
+    if err != nil {
+            return err
+    }
+    if block && unblock {
+        return errors.New("Can't block and unblock at the same time!\n")
+    }
+
+    var userStatusNew string 
+    if block {
+        userShellNew    = "/bin/false"
+        userStatusNew   = "Blocked!"
+        userPasswordNew = "auto"
+    } else if unblock {
+        userShellNew    = "/bin/bash"
+        userStatusNew   = "Free!"
+        userPasswordNew = "auto"
+    }
+
+    userMod := model.User{
+            Name:     userNameNew,
+            Group:    userGroupNew,
+            GRR:      userGRRNew,
+            Path:     userPathNew,
+            Login:    userLogin,
+            Shell:    userShellNew,
+            Homedir:  userHomedirNew,
+            Password: userPasswordNew,
+            Status:   userStatusNew,
+    }
+
+    if cmd.Flags().Changed("password") || block || unblock {
+        if userPasswordNew == "auto" {
+            userPasswordNew = generatePassword()
+            userMod.Password = "[auto-generated]"
+        } else {
+            userMod.Password = "[explicitly set]"
+        }
+    } else {
+        userMod.Password = "[unchanged]"
+    }
+
+    fmt.Println(userMod.ToString())
+
+    if !confirm {
+        fmt.Print("Proceed with user update? [y/N] ")
+        reader := bufio.NewReader(os.Stdin)
+        response, _ := reader.ReadString('\n')
+        if strings.TrimSpace(strings.ToLower(response)) != "y" {
+            fmt.Fprintln(os.Stderr, "Aborted.")
+            os.Exit(1)
+        }
+    }
+
+    fmt.Println("Done!")
+
+    fmt.Println(userPasswordNew)
+
+    return nil
+}
+
+func generatePassword() string {
+    const charset = "@*()=+[];,.?123456789abcdefghijkmnopqrstuvwxyzABCDEFGHJKLMNPQRSTUVWXYZ"
+    b := make([]byte, 20)
+    if _, err := rand.Read(b); err != nil {
+        panic(err)
+    }
+    for i := range b {
+        b[i] = charset[int(b[i])%len(charset)]
+    }
+    return string(b)
 }
+
diff --git a/model/user.go b/model/user.go
index 0036ff1..116f8e9 100644
--- a/model/user.go
+++ b/model/user.go
@@ -4,12 +4,13 @@ import "fmt"
 
 type User struct {
 	GRR      string
-	Ltype    string
 	Path     string
 	Name     string
+	Ltype    string
 	Login    string
 	Group    string
 	Shell    string
+    Status   string
 	Homedir  string
 	Password string
 }
@@ -23,6 +24,7 @@ func (u *User) ToString() string {
 	Shell:    %s
 	HomeDir:  %s
 	Password: %s
-	WebPath:  %s`,
-		u.Name, u.Login, u.Ltype, u.Group, u.Shell, u.Homedir, u.Password, u.Path)
+	WebPath:  %s
+	Status:   %s`,
+		u.Name, u.Login, u.Ltype, u.Group, u.Shell, u.Homedir, u.Password, u.Path, u.Status)
 }
-- 
GitLab