From 991b71f46c167b1748e545de7f47dd193f81b7e0 Mon Sep 17 00:00:00 2001 From: tss24 <tss24@inf.ufpr.br> Date: Thu, 27 Feb 2025 19:02:23 -0300 Subject: [PATCH] Finish bulk prototype and remove course option --- README.md | 8 +- cmd/user.go | 5 +- cmd/user/bulk.go | 178 +++++++++++++++++++++++++++++++++++++++++++++ cmd/user/create.go | 73 ++++++++++--------- cmd/user/mod.go | 19 +---- cmd/user/remove.go | 17 +++-- cmd/user/show.go | 24 +++--- model/opts.go | 32 +++++--- model/user.go | 9 +-- 9 files changed, 272 insertions(+), 93 deletions(-) create mode 100644 cmd/user/bulk.go diff --git a/README.md b/README.md index e24a67a..a44b3a8 100644 --- a/README.md +++ b/README.md @@ -48,16 +48,16 @@ To create a user you can use: the command needs some basic info to create a user. The baseline is this: - useradm user create -n "<User Full Name>" -g <User group> -c <User course> + useradm user create -n "<User Full Name>" -g <User group> but if we run something like - useradm user create -n "Joao da Silva" -g bcc -c bcc + useradm user create -n "Joao da Silva" -g bcc we will get an error. That is because the login generation needs the user GRR for the default 'ini' login type. So this is now correct: - useradm user create -n "Joao da Silva" -g bcc -c bcc -t last + useradm user create -n "Joao da Silva" -g bcc -t last the auto generation will be explained in the section "Implementation" @@ -99,8 +99,6 @@ To reset a user's password you can do: with no flag a password will be auto-generated. Passing the flag -p you can specify the new password. Passwords need to be good lol. -Note: Check "Implementation" to learn about kadmin.local command - ### Bulk TODO :)))))) diff --git a/cmd/user.go b/cmd/user.go index 0b3ae43..21cfa72 100644 --- a/cmd/user.go +++ b/cmd/user.go @@ -14,9 +14,10 @@ new users, please do so with the bulk subcommand.`, } func init() { - userCmd.AddCommand(user.CreateUserCmd) - userCmd.AddCommand(user.RemoveUserCmd) userCmd.AddCommand(user.ModCmd) userCmd.AddCommand(user.ShowCmd) + userCmd.AddCommand(user.BulkCmd) userCmd.AddCommand(user.ResetCmd) + userCmd.AddCommand(user.CreateCmd) + userCmd.AddCommand(user.RemoveCmd) } diff --git a/cmd/user/bulk.go b/cmd/user/bulk.go new file mode 100644 index 0000000..9b45291 --- /dev/null +++ b/cmd/user/bulk.go @@ -0,0 +1,178 @@ +package user + +// TODO: PQ Q N FUNCIONA?????????????? +import ( + "fmt" + "os" + "strconv" + + "github.com/spf13/cobra" + "gitlab.c3sl.ufpr.br/tss24/useradm/model" +) + +var BulkCmd = &cobra.Command{ + Use: "bulk", + Short: "Create a lot of similar users", + RunE: bulkCreate, +} + +func init() { + BulkCmd.Flags().StringP("passwd", "p", "", "Base password, will generate <password>#[1..number]") + BulkCmd.Flags().StringP("login", "l", "", "Base login, will generate <login>[1..number]") + BulkCmd.Flags().StringP("expiry", "e", "_", "Accounts' expiry date (format dd.mm.yy)") + BulkCmd.Flags().StringP("resp", "r", "_", "Person responsible for the accounts") + BulkCmd.Flags().StringP("group", "g", "", "Base group of the accounts") + BulkCmd.Flags().IntP("number", "n", 0, "Number of accounts to be created") + + BulkCmd.MarkFlagRequired("login") + BulkCmd.MarkFlagRequired("passwd") + BulkCmd.MarkFlagRequired("group") + BulkCmd.MarkFlagRequired("number") +} + +func bulkCreate(cmd *cobra.Command, args []string) error { + var opts model.Opts + var users []model.User + + users, err := getUsers() + if err != nil { + return err + } + + err = opts.RetrieveOpts(cmd) + if err != nil { + return err + } + + for i := 1; i <= opts.Number; i++ { + if loginExists(users, opts.UID+strconv.Itoa(i)) { + return fmt.Errorf("User found with login %v%v, won't overwrite", opts.UID, i) + } + } + + err = validateGID(opts.GID) + if err != nil { + return err + } + + base := model.User{ + GRR: "_", + UID: opts.UID, + GID: opts.GID, + Name: "_", + Resp: opts.Resp, + Ltype: "_", + Shell: "/bin/bash", + Status: "Active", + Expiry: opts.Expiry, + Webdir: "_", + Password: opts.Passwd + "#", + } + + base.Homedir, err = genDirPath("/home", base.GID, base.UID, "") + if err != nil { + return err + } + + base.Nobackup, err = genDirPath("/nobackup", base.GID, base.UID, "") + if err != nil { + return err + } + + i := 1 + success := false + + defer func() { + if !success { + fmt.Println("Error found, cleaning up...") + for ; i > 0; i-- { + istring := strconv.Itoa(i) + _ = delKerberosPrincipal(base.UID + istring) + _ = delUserLDAP(base.UID + istring) + _ = os.RemoveAll(base.Nobackup + istring) + _ = os.RemoveAll(base.Homedir + istring) + } + } + }() + + for ; i <= opts.Number; i++ { + err := createTempUser(base, i) + if err != nil { + return err + } + } + + success = true + return nil +} + +func createTempUser(base model.User, num int) error { + var err error + var groups map[string]string + numstring := strconv.Itoa(num) + + groups, err = getGroups() + if err != nil { + return err + } + + // gen login + base.UID = base.UID + numstring + + // gen dn + base.DN = "uid=" + base.UID + ",ou=usuarios,dc=c3local" + + // no webdir for temps + + // gen home path + base.Homedir = base.Homedir + numstring + + // gen nobkp path + base.Nobackup = base.Nobackup + numstring + + // gen password + base.Password = base.Password + numstring + + // gen gecos + base.Gecos = genGecos(base) + + // get group id + base.GIDNumber, err = findGIDNumber(groups, base.GID) + if err != nil { + return err + } + + // gen newuid + base.UIDNumber, err = getNewUIDNumber() + if err != nil { + return err + } + + fmt.Printf("Pronto para criar:\n%v\n\n", base.FullToString()) + confirmationPrompt(false, "creation") + + // create ldap + err = addUserLDAP(base) + if err != nil { + return err + } + // create kerberos + err = addKerberosPrincipal(base.UID) + if err != nil { + return err + } + + // change pass + err = modKerberosPassword(base.UID, base.Password) + if err != nil { + return err + } + + // create dirs + err = createUserDirs(base) + if err != nil { + return err + } + + return nil +} diff --git a/cmd/user/create.go b/cmd/user/create.go index a7bd49e..7b4c9b5 100644 --- a/cmd/user/create.go +++ b/cmd/user/create.go @@ -23,7 +23,7 @@ const ( MAX_UID = 6000 ) -var CreateUserCmd = &cobra.Command{ +var CreateCmd = &cobra.Command{ Use: "create", Short: "Create new user", RunE: createUserFunc, @@ -32,28 +32,25 @@ var CreateUserCmd = &cobra.Command{ func init() { // possible flags // FIXME: maybe leave less flags for user input - CreateUserCmd.Flags().StringP("grr", "r", "_", "User GRR, required for ini type") - CreateUserCmd.Flags().StringP("type", "t", "ini", "Type of auto-generated login: ini, first or last") - CreateUserCmd.Flags().StringP("path", "w", "", "Full path to webdir, /home/html/inf/login if empty") - CreateUserCmd.Flags().StringP("name", "n", "_", "User full name, required, use quotes for spaces") - CreateUserCmd.Flags().StringP("login", "l", "", "User login name, auto-generated if empty") - CreateUserCmd.Flags().StringP("group", "g", "", "User base group, required") - CreateUserCmd.Flags().StringP("shell", "s", "/bin/bash", "Full path to shell") - CreateUserCmd.Flags().StringP("homedir", "d", "", "Home directory path, /home/group/login if empty") - CreateUserCmd.Flags().StringP("passwd", "p", "", "User password, auto-generated if empty") - CreateUserCmd.Flags().StringP("status", "a", "Active", "User status, Active or Blocked") - CreateUserCmd.Flags().StringP("resp", "i", "_", "Person responsible for the account") - CreateUserCmd.Flags().StringP("expiry", "e", "_", "Expiry date in format dd.mm.yy") - CreateUserCmd.Flags().StringP("course", "c", "_", "User course/minicourse, even temp ones") - CreateUserCmd.Flags().StringP("nobkp", "b", "", "User nobackup directory path") + CreateCmd.Flags().StringP("grr", "r", "_", "User GRR, required for ini type") + CreateCmd.Flags().StringP("type", "t", "ini", "Type of auto-generated login: ini, first or last") + CreateCmd.Flags().StringP("path", "w", "", "Full path to webdir, /home/html/inf/login if empty") + CreateCmd.Flags().StringP("name", "n", "_", "User full name, required, use quotes for spaces") + CreateCmd.Flags().StringP("login", "l", "", "User login name, auto-generated if empty") + CreateCmd.Flags().StringP("group", "g", "", "User base group, required") + 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("passwd", "p", "", "User password, auto-generated if empty") + CreateCmd.Flags().StringP("status", "a", "Active", "User status, Active or Blocked") + CreateCmd.Flags().StringP("resp", "i", "_", "Person responsible for the account") + CreateCmd.Flags().StringP("expiry", "e", "_", "Expiry date in format dd.mm.yy") + CreateCmd.Flags().StringP("nobkp", "b", "", "User nobackup directory path") // required flags - CreateUserCmd.MarkFlagRequired("name") - CreateUserCmd.MarkFlagRequired("group") - // made it required, may be overkill... - CreateUserCmd.MarkFlagRequired("course") + CreateCmd.MarkFlagRequired("name") + CreateCmd.MarkFlagRequired("group") - CreateUserCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") + CreateCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") } func createUserFunc(cmd *cobra.Command, args []string) error { @@ -67,7 +64,7 @@ func createUserFunc(cmd *cobra.Command, args []string) error { defer func() { if !success { _ = delKerberosPrincipal(usr.UID) - _ = delUserLDAP(usr) + _ = delUserLDAP(usr.UID) } }() @@ -173,28 +170,24 @@ func createNewUserModel(cmd *cobra.Command) (model.User, bool, error) { Shell: opts.Shell, Status: opts.Status, Expiry: opts.Expiry, - Course: opts.Course, Webdir: opts.Webdir, Homedir: opts.Homedir, Nobackup: opts.Nobkp, Password: ifThenElse(opts.Passwd != "", opts.Passwd, "[auto-generate]"), } - u = genGecos(u) + u.Gecos = genGecos(u) // get a new UIDNumber - newUIDNumber, err := getNewUIDNumber() + u.UIDNumber, err = getNewUIDNumber() if err != nil { return u, false, fmt.Errorf("failed to generate new UIDNumber for user: %v", err) } - u.UIDNumber = newUIDNumber // assign GIDNumber by traversing the groups - for key, val := range groups { - if val == u.GID { - u.GIDNumber = key - break - } + u.GIDNumber, err = findGIDNumber(groups, u.GID) + if err != nil { + return u, false, err } u.DN = "uid=" + u.UID + ",ou=usuarios,dc=c3local" @@ -210,18 +203,16 @@ func genDirPath(base, group, login, input string) (string, error) { return p, validatePath(p) } -func genGecos(u model.User) model.User { +func genGecos(u model.User) string { gecos := u.Name + "," gecos += u.GRR + "," gecos += u.Resp + "," - gecos += u.Course + "," gecos += u.Expiry + "," gecos += u.Status + "," gecos += u.Ltype + "," gecos += u.Webdir + "," gecos += u.Nobackup - u.Gecos = gecos - return u + return gecos } type LoginType int @@ -328,6 +319,15 @@ func genUniqueUID(name, grr string, ltypeString string, users []model.User) (str return uid, nil } +func findGIDNumber(groups map[string]string, GID string) (string, error) { + for key, val := range groups { + if val == GID { + return key, nil + } + } + return "", fmt.Errorf("Couldn't find group GIDNumber") +} + // queries to check if the alias exists func mailAliasExists(alias string) bool { cmd := exec.Command("/usr/sbin/postalias", "-q", alias, MAIL_ALIAS_FILE) @@ -482,6 +482,11 @@ func createHome(u model.User, homeDir string) error { func createWeb(u model.User) error { success := false + if u.Webdir == "_" { + success = true + return nil + } + perm := DEF_PERMISSION if u.Status == "Blocked" { perm = BLK_PERMISSION diff --git a/cmd/user/mod.go b/cmd/user/mod.go index 6792f92..2c06d20 100644 --- a/cmd/user/mod.go +++ b/cmd/user/mod.go @@ -19,7 +19,6 @@ type cfg struct { Group string `yaml:"Group"` Status string `yaml:"Status"` Shell string `yaml:"Shell"` - Course string `yaml:"Course"` Resp string `yaml:"Resp"` Expiry string `yaml:"Expiry"` } @@ -37,25 +36,17 @@ func init() { } func modifyUserFunc(cmd *cobra.Command, args []string) error { - var users []model.User var opts model.Opts - users, err := getUsers() - if err != nil { - return err - } - err = opts.RetrieveOpts(cmd) + err := opts.RetrieveOpts(cmd) if err != nil { return err } - login := args[0] - res := searchUser(users, false, true, login, "", "", "", "", "") - if len(res) != 1 { - err = fmt.Errorf("More than one user found") + curr, err := locateUser(args[0]) + if err != nil { return err } - curr := res[0] state := cfg{ Name: curr.Name, @@ -63,7 +54,6 @@ func modifyUserFunc(cmd *cobra.Command, args []string) error { Group: curr.GID, Status: curr.Status, Shell: curr.Shell, - Course: curr.Course, Resp: curr.Resp, Expiry: curr.Expiry, } @@ -177,7 +167,7 @@ func genRequest(curr model.User, changes cfg) (*ldap.ModifyRequest, error) { changed := applyChangesToUser(curr, changes) - changed = genGecos(changed) + changed.Gecos = genGecos(changed) // changed gecos if curr.Gecos != changed.Gecos { @@ -194,7 +184,6 @@ func applyChangesToUser(c model.User, n cfg) model.User { c.Resp = n.Resp c.Shell = n.Shell c.Status = n.Status - c.Course = n.Course c.Expiry = n.Expiry return c } diff --git a/cmd/user/remove.go b/cmd/user/remove.go index 499c5b3..0c10753 100644 --- a/cmd/user/remove.go +++ b/cmd/user/remove.go @@ -21,7 +21,7 @@ var ( WEB_TRASH = "/home/contas_removidas/html/" + ANO ) -var RemoveUserCmd = &cobra.Command{ +var RemoveCmd = &cobra.Command{ Use: "remove [username]", Short: "Delete a user", Args: cobra.ExactArgs(1), @@ -29,7 +29,7 @@ var RemoveUserCmd = &cobra.Command{ } func init() { - RemoveUserCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") + RemoveCmd.Flags().BoolP("confirm", "y", false, "Skip confirmation prompt") } func removeUserFunc(cmd *cobra.Command, args []string) error { @@ -68,7 +68,7 @@ func removeUserFunc(cmd *cobra.Command, args []string) error { return err } - err = delUserLDAP(u) + err = delUserLDAP(u.UID) if err != nil { return err } @@ -91,7 +91,7 @@ func locateUser(login string) (model.User, error) { filter := searchUser(users, false, true, login, "", "", "", "", "") if len(filter) != 1 { return u, fmt.Errorf(`More than one user matched the login given. -search made: "useradm user show -l %v"`, login) +search made: "useradm user show -l %v -e"`, login) } u = filter[0] @@ -118,7 +118,7 @@ func removeDirs(u model.User) error { return nil } -func delUserLDAP(u model.User) error { +func delUserLDAP(UID string) error { l, err := connLDAP() if err != nil { return err @@ -130,7 +130,7 @@ func delUserLDAP(u model.User) error { "ou=grupos,dc=c3local,dc=com", ldap.ScopeWholeSubtree, ldap.NeverDerefAliases, 0, 0, false, - "(memberUid="+u.UID+")", // Filter by UID membership + "(memberUid="+UID+")", // Filter by UID membership []string{"dn", "cn"}, nil, ) @@ -143,13 +143,14 @@ func delUserLDAP(u model.User) error { // iterate and remove user from each group for _, entry := range groups.Entries { groupName := entry.GetAttributeValue("cn") - if err := delUserFromGroupLDAP(l, u.UID, groupName); err != nil { + if err := delUserFromGroupLDAP(l, UID, groupName); err != nil { log.Printf("Warning: %v", err) } } // removing user entry - delReq := ldap.NewDelRequest(u.DN, nil) + userDN := "uid=" + UID + "ou=usuarios,dc=c3local" + delReq := ldap.NewDelRequest(userDN, nil) if err := l.Del(delReq); err != nil && !ldap.IsErrorWithCode(err, ldap.LDAPResultNoSuchObject) { return fmt.Errorf("User deletion failed: %v", err) diff --git a/cmd/user/show.go b/cmd/user/show.go index 6de2eee..5e9ad4f 100644 --- a/cmd/user/show.go +++ b/cmd/user/show.go @@ -214,13 +214,12 @@ func getLDAPPassword(path string) (string, error) { // 0. users full name // 1. users grr // 2. responsible professor -// 3. users course/minicourse (even temp) -// 4. account expiry date (may be used for deletion) -// 5. account status (Blocked/Active) -// 6. login type (ini, first or last) -// 7. users webdir -// 8. users nobackup dir -// 9. to be continued... +// 3. account expiry date (may be used for deletion) +// 4. account status (Blocked/Active) +// 5. login type (ini, first or last) +// 6. users webdir +// 7. users nobackup dir +// 8. to be continued... func parseGecos(user model.User) model.User { result := [NUM_GECOS_FIELDS]string{0: "_"} for i := range result { @@ -236,15 +235,14 @@ func parseGecos(user model.User) model.User { user.GRR = result[1] user.Resp = result[2] - user.Course = result[3] - user.Expiry = result[4] + user.Expiry = result[3] // only set with gecos if empty if user.Status == "_" { - user.Status = result[5] + user.Status = result[4] } - user.Ltype = result[6] - user.Webdir = result[7] - user.Nobackup = result[8] + user.Ltype = result[5] + user.Webdir = result[6] + user.Nobackup = result[7] return user } diff --git a/model/opts.go b/model/opts.go index 93d947a..a2f2768 100644 --- a/model/opts.go +++ b/model/opts.go @@ -18,7 +18,6 @@ type Opts struct { Passwd string Webdir string Status string - Course string Expiry string Homedir string Ignore bool @@ -26,6 +25,7 @@ type Opts struct { Block bool Unblock bool Confirm bool + Number int } func (o *Opts) ToString() string { @@ -41,17 +41,17 @@ func (o *Opts) ToString() string { Passwd %s Webdir %s Status %s - Course %s Expiry %s Homedir %s Ignore %v Exact %v Block %v Unblock %v - Confirm %v`, + Confirm %v + Number %v`, o.GRR, o.GID, o.UID, o.Resp, o.Name, o.Nobkp, o.Ltype, o.Shell, - o.Passwd, o.Webdir, o.Status, o.Course, o.Expiry, o.Homedir, o.Ignore, - o.Exact, o.Block, o.Unblock, o.Confirm) + o.Passwd, o.Webdir, o.Status, o.Expiry, o.Homedir, o.Ignore, + o.Exact, o.Block, o.Unblock, o.Confirm, o.Number) } func (o *Opts) RetrieveOpts(cmd *cobra.Command) error { @@ -107,11 +107,6 @@ func (o *Opts) RetrieveOpts(cmd *cobra.Command) error { return err } - o.Course, err = getFlagString(cmd, "course") - if err != nil { - return err - } - o.Expiry, err = getFlagString(cmd, "expiry") if err != nil { return err @@ -152,6 +147,11 @@ func (o *Opts) RetrieveOpts(cmd *cobra.Command) error { return err } + o.Number, err = getFlagInt(cmd, "number") + if err != nil { + return err + } + return nil } @@ -172,6 +172,18 @@ func getFlagString(cmd *cobra.Command, flag string) (string, error) { return flagValue, nil } +func getFlagInt(cmd *cobra.Command, flag string) (int, error) { + if cmd.Flags().Lookup(flag) == nil { + return 0, nil + } + + flagValue, err := cmd.Flags().GetInt(flag) + if err != nil { + return 0, fmt.Errorf("failed to get flag %q: %w", flag, err) + } + return flagValue, nil +} + func getFlagBool(cmd *cobra.Command, flag string) (bool, error) { if cmd.Flags().Lookup(flag) == nil { return false, nil diff --git a/model/user.go b/model/user.go index 1bce7ae..0fc75b1 100644 --- a/model/user.go +++ b/model/user.go @@ -12,7 +12,6 @@ type User struct { Ltype string Gecos string Shell string - Course string Status string Webdir string Expiry string @@ -35,10 +34,9 @@ func (u *User) ToString() string { Nobkp: %s Status: %s Resp: %s - Course: %s Expiry: %s`, u.Name, u.UID, u.GRR, u.GID, u.Shell, u.Homedir, u.Webdir, - u.Nobackup, u.Status, u.Resp, u.Course, u.Expiry) + u.Nobackup, u.Status, u.Resp, u.Expiry) } // useful for debugging :) @@ -53,7 +51,6 @@ func (u *User) FullToString() string { Ltype: %s Gecos: %s Shell: %s - Course: %s Status: %s Webdir: %s Expiry: %s @@ -63,6 +60,6 @@ func (u *User) FullToString() string { GIDNumber: %s UIDNumber: %s`, u.DN, u.GRR, u.UID, u.GID, u.Name, u.Resp, u.Ltype, u.Gecos, - u.Shell, u.Course, u.Status, u.Webdir, u.Expiry, u.Homedir, - u.Password, u.Nobackup, u.GIDNumber, u.UIDNumber) + u.Shell, u.Status, u.Webdir, u.Expiry, u.Homedir, u.Password, + u.Nobackup, u.GIDNumber, u.UIDNumber) } -- GitLab