package user import ( "fmt" "os" "os/exec" "github.com/go-ldap/ldap/v3" "github.com/spf13/cobra" "gitlab.c3sl.ufpr.br/tss24/useradm/internal/auth" "gitlab.c3sl.ufpr.br/tss24/useradm/internal/entities/opts" "gitlab.c3sl.ufpr.br/tss24/useradm/internal/entities/user" "gitlab.c3sl.ufpr.br/tss24/useradm/internal/manage" "gitlab.c3sl.ufpr.br/tss24/useradm/internal/validate" "gitlab.c3sl.ufpr.br/tss24/useradm/pkg/utils" "gopkg.in/yaml.v3" ) type cfg struct { Name string `yaml:"Name"` GRR string `yaml:"GRR"` Group string `yaml:"Group"` Status string `yaml:"Status"` Shell string `yaml:"Shell"` Resp string `yaml:"Resp"` Expiry string `yaml:"Expiry"` } var ModCmd = &cobra.Command{ Use: "mod [username]", Short: "Modify user information", Long: `Opens a file for editing the users config. Uses $EDITOR variable, if it is not set, use vim`, Args: cobra.ExactArgs(1), RunE: modUserCmd, } func init() { ModCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") } func modUserCmd(cmd *cobra.Command, args []string) error { var opts opts.Opts l, err := auth.ConnLDAP() if err != nil { return err } defer l.Close() users, err := manage.GetAllUsers(l) if err != nil { return err } groups, err := auth.GetAllGroupsLDAP(l) if err != nil { return err } err = opts.RetrieveOpts(cmd) if err != nil { return err } curr, err := manage.Locate(l, args[0]) if err != nil { return err } state := cfg{ Name: curr.Name, GRR: curr.GRR, Group: curr.GID, Status: curr.Status, Shell: curr.Shell, Resp: curr.Resp, Expiry: curr.Expiry, } changes, err := promptUserYaml(state) if changes.GRR != curr.GRR { err := validate.GRR(users, changes.GRR) if err != nil { return err } } err = validate.GroupUnique(groups, changes.Group) if err != nil { return err } err = validate.Status(changes.Status) if err != nil { return err } err = validate.Expiry(changes.Expiry) if err != nil { return err } oldGroup := curr.GID req, err := genRequest(groups, curr, changes) if err != nil { return err } fmt.Printf("%v\n\n", curr.ToString()) utils.ConfirmationPrompt(opts.Confirm, "user update") if err := l.Modify(req); err != nil { return fmt.Errorf("Failed to update user attributes: %v", err) } if oldGroup != changes.Group { if err := auth.DelFromGroupLDAP(l, curr.UID, curr.GID); err != nil { return err } if err := auth.AddToGroupLDAP(l, curr.UID, changes.Group); err != nil { return err } } if err := utils.ClearCache(); err != nil { fmt.Printf(`Failed to reload cache! all is ok but may take a while to apply the changes Output: %v`, err) } else { fmt.Printf("Changes applied!\n") } return nil } func genRequest(groups map[string]string, curr *user.User, changes cfg) (*ldap.ModifyRequest, error) { req := ldap.NewModifyRequest(curr.DN, nil) // changed name if curr.Name != changes.Name { req.Replace("cn", []string{changes.Name}) } // changed status if curr.Status != changes.Status { if changes.Status == "Active" && changes.Shell == "/bin/false" { req.Replace("loginShell", []string{"/bin/bash"}) } else if changes.Status == "Active" { req.Replace("loginShell", []string{changes.Shell}) } else { req.Replace("loginShell", []string{"/bin/false"}) } } else { // only changed shell if curr.Shell != changes.Shell { req.Replace("loginShell", []string{changes.Shell}) } } // changed base group if curr.GID != changes.Group { var gidNumber string for id, gid := range groups { if gid == changes.Group { gidNumber = id break } } req.Replace("gidNumber", []string{gidNumber}) } gecos := curr.Gecos applyChangesToUser(curr, changes) curr.GenGecos() // changed gecos if curr.Gecos != gecos { req.Replace("gecos", []string{curr.Gecos}) } return req, nil } func applyChangesToUser(c *user.User, n cfg) { c.GRR = n.GRR c.GID = n.Group c.Name = n.Name c.Resp = n.Resp c.Shell = n.Shell c.Status = n.Status c.Expiry = n.Expiry } // generates a yaml file of a config state for the user to edit func promptUserYaml(state cfg) (cfg, error) { var newState cfg tmpFile, err := os.CreateTemp("", "config-*.yaml") if err != nil { err = fmt.Errorf("Error trying to create temp file: %v", err) return newState, err } defer os.Remove(tmpFile.Name()) data, err := yaml.Marshal(state) if err != nil { return newState, fmt.Errorf("Error serializing the yaml: %v", err) } if err := os.WriteFile(tmpFile.Name(), data, 0644); err != nil { return newState, fmt.Errorf("Error writing to temp file: %v", err) } // abrir o editor editor := os.Getenv("EDITOR") if editor == "" { editor = "vim" } comd := exec.Command(editor, tmpFile.Name()) comd.Stdin = os.Stdin comd.Stdout = os.Stdout comd.Stderr = os.Stderr if err := comd.Run(); err != nil { return newState, fmt.Errorf("Error opening the editor: %v", err) } editedData, err := os.ReadFile(tmpFile.Name()) if err != nil { return newState, fmt.Errorf("Error reading the modified file: %v", err) } if err := yaml.Unmarshal(editedData, &newState); err != nil { return newState, fmt.Errorf("Error de-serializing the yaml: %v", err) } return newState, nil }