Loading NOTES.md +2 −2 Original line number Diff line number Diff line Loading @@ -35,11 +35,11 @@ Switch between MAIL and BULLETIN modes? MAIL commands are documented * Database * trigger to limit values for 'visibility'? * Add commands: * A way to add / delete ssh keys. * ~~A way to add / delete ssh keys.~~ * A way to manage files? * Commands for a local mail system? * Commands to connect to Mattermost or mastodon? * Commands to manage users. * ~~Commands to manage users.~~ * Handle MARK for SELECT and DIRECTORY. Done: Loading ask/ask.go +23 −0 Original line number Diff line number Diff line Loading @@ -5,8 +5,10 @@ getting a line of text, getting a choice from a liat and other things. package ask import ( "errors" "fmt" "os" "strconv" "github.com/chzyer/readline" ) Loading @@ -29,3 +31,24 @@ func GetLine(prompt string) (string, error) { line, err := rl.Readline() return line, err } // Choose presents a list and asks a user to choose one. func Choose(prompt string, choices []string) (int, error) { fmt.Println(prompt) for i := range choices { fmt.Printf(" %d. %s\n", i+1, choices[i]) } response, err := GetLine("Choose a number or enter q to abort: ") if err != nil { return -1, err } choice, err := strconv.Atoi(response) if err != nil { return -1, nil } choice-- if choice < 0 || choice >= len(choices) { return -1, errors.New("Choice out of bounds") } return choice, nil } key/key.go +151 −4 Original line number Diff line number Diff line Loading @@ -2,24 +2,38 @@ package key import ( "bufio" "bytes" "errors" "fmt" "io" "os" "path" "strings" "github.com/adrg/xdg" "golang.org/x/crypto/ssh" "golang.org/x/sys/unix" ) var keytemplate = `command="%s -u %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s\n` var keytemplate = `command="%s -u %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s` // Add adds an ssh key to the `authorized_keys` file. func Add(login, public string) error { bulletin, err := os.Executable() // Parse and verify the key. public = strings.TrimSpace(public) theKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(public)) if err != nil { return err } keyline := fmt.Sprintf(keytemplate, bulletin, login, public) // Find the bulletin binary. bulletin, err := os.Executable() if err != nil { return err } // File system management. sshdir := path.Join(xdg.Home, ".ssh") err = os.MkdirAll(sshdir, 0700) if err != nil { Loading @@ -27,11 +41,28 @@ func Add(login, public string) error { } keyfile := path.Join(sshdir, "authorized_keys") f, err := os.OpenFile(keyfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) // Open and lock the authorized_keys file. f, err := os.OpenFile(keyfile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } defer f.Close() if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { return err } defer unix.Flock(int(f.Fd()), unix.LOCK_UN) // unlock after we're done // Check for duplicates. keycontent, err := io.ReadAll(f) if err != nil { return err } if bytes.Contains(keycontent, ssh.MarshalAuthorizedKey(theKey)) { return errors.New("key already exists") } // Generate and write the key. keyline := fmt.Sprintf(keytemplate, bulletin, login, string(ssh.MarshalAuthorizedKey(theKey))) n, err := f.WriteString(keyline) if err != nil { return err Loading @@ -43,3 +74,119 @@ func Add(login, public string) error { return nil } // List returns a list of ssh keys for this user. func List(login string) ([]string, error) { keys := []string{} // Find the bulletin binary. bulletin, err := os.Executable() if err != nil { return keys, err } // File system management. sshdir := path.Join(xdg.Home, ".ssh") err = os.MkdirAll(sshdir, 0700) if err != nil { return keys, err } keyfile := path.Join(sshdir, "authorized_keys") // Open the authorized_keys file. f, err := os.OpenFile(keyfile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return keys, err } defer f.Close() // look for lines. scanner := bufio.NewScanner(f) for scanner.Scan() { keyline := bytes.TrimSpace(scanner.Bytes()) if len(keyline) == 0 { continue } public, _, options, _, err := ssh.ParseAuthorizedKey([]byte(keyline)) if err != nil { return keys, err } for i := range options { opts := strings.SplitN(options[i], "=", 2) if len(opts) != 2 || opts[0] != "command" { continue } cmd := strings.Split(strings.Trim(opts[1], "\" "), " ") if len(cmd) != 3 { return keys, fmt.Errorf("Unexpected command in authorized keys file (%s)", opts[1]) } if cmd[0] != bulletin { return keys, fmt.Errorf("Unexpected bulletin in authorized keys file (%s)", opts[1]) } if cmd[1] != "-u" { return keys, fmt.Errorf("Unexpected flag in authorized keys file (%s)", opts[1]) } if cmd[2] == login { keys = append(keys, strings.Trim(string(ssh.MarshalAuthorizedKey(public)), "\n")) } break } } return keys, nil } // Delete removes the key. func Delete(public string) error { keys := []string{} // Parse and verify the key. public = strings.TrimSpace(public) doomedRaw, _, _, _, err := ssh.ParseAuthorizedKey([]byte(public)) if err != nil { return err } // File system management. sshdir := path.Join(xdg.Home, ".ssh") err = os.MkdirAll(sshdir, 0700) if err != nil { return err } keyfile := path.Join(sshdir, "authorized_keys") // Open the authorized_keys file. f, err := os.OpenFile(keyfile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } defer f.Close() if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { return err } defer unix.Flock(int(f.Fd()), unix.LOCK_UN) // unlock after we're done // look for lines. doomed := doomedRaw.Marshal() scanner := bufio.NewScanner(f) for scanner.Scan() { keyline := bytes.TrimSpace(scanner.Bytes()) if len(keyline) == 0 { continue } potential, _, _, _, err := ssh.ParseAuthorizedKey(keyline) if err != nil { return err } if bytes.Compare(potential.Marshal(), doomed) != 0 { keys = append(keys, string(keyline)) } } if _, err := f.Seek(0, os.SEEK_SET); err != nil { return fmt.Errorf("seek: %w", err) } f.Truncate(0) f.WriteString(strings.Join(keys, "\n") + "\n") return nil } repl/accounts.go +226 −31 Original line number Diff line number Diff line Loading @@ -2,8 +2,14 @@ package repl import ( "fmt" "strings" "git.lyda.ie/kevin/bulletin/ask" "git.lyda.ie/kevin/bulletin/dclish" "git.lyda.ie/kevin/bulletin/key" "git.lyda.ie/kevin/bulletin/storage" "git.lyda.ie/kevin/bulletin/this" "git.lyda.ie/kevin/bulletin/users" ) // ActionUser handles the `USER` command. Loading @@ -12,83 +18,272 @@ func ActionUser(cmd *dclish.Command) error { fmt.Println(` The following commands are available: ADD ADMIN DELETE DISABLE ENABLE MOD NOADMIN NOMOD`) ADD ADMIN DELETE DISABLE ENABLE LIST MOD NOADMIN NOMOD`) fmt.Println() return nil } // ActionUserAdd handles the `USER ADD` command. func ActionUserAdd(_ *dclish.Command) error { // TODO: implement func ActionUserAdd(cmd *dclish.Command) error { login := strings.ToUpper(cmd.Args[0]) err := users.ValidLogin(login) if err != nil { fmt.Printf("ERROR: %s.\n", err) return nil } return nil } // ActionUserList handles the `USER LIST` command. func ActionUserList(_ *dclish.Command) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } ctx := storage.Context() userlist, err := this.Q.ListUsers(ctx) if err != nil { fmt.Printf("ERROR: Failed to list users (%s).\n", err) return nil } // TODO: nicer output for user. for _, u := range userlist { fmt.Printf("%s\n", u) } return nil } // ActionUserDelete handles the `USER DELETE` command. func ActionUserDelete(_ *dclish.Command) error { // TODO: implement func ActionUserDelete(cmd *dclish.Command) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } ctx := storage.Context() err = this.Q.DeleteUser(ctx, u.Login) if err != nil { fmt.Printf("ERROR: Failed to delete user (%s).\n", err) return nil } fmt.Println("User deleted.") return nil } // ActionUserEnable handles the `USER ENABLE` command. func ActionUserEnable(_ *dclish.Command) error { // TODO: implement func actionUserEnable(cmd *dclish.Command, disabled int64, doing string) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } if u.Disabled == disabled { fmt.Printf("User already %sd.\n", doing) return nil } ctx := storage.Context() err = this.Q.UpdateUserDisabled(ctx, storage.UpdateUserDisabledParams{ Login: u.Login, Disabled: disabled, }) if err != nil { fmt.Printf("ERROR: Failed to %s user (%s).\n", doing, err) return nil } fmt.Printf("User %sd.\n", doing) return nil } // ActionUserEnable handles the `USER ENABLE` command. func ActionUserEnable(cmd *dclish.Command) error { return actionUserEnable(cmd, 0, "enable") } // ActionUserDisable handles the `USER DISABLE` command. func ActionUserDisable(_ *dclish.Command) error { // TODO: implement func ActionUserDisable(cmd *dclish.Command) error { return actionUserEnable(cmd, 1, "disable") } func actionUserAdmin(cmd *dclish.Command, admin int64, doing string) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } if u.Admin == admin { fmt.Printf("User is already %s.\n", doing) return nil } ctx := storage.Context() err = this.Q.UpdateUserAdmin(ctx, storage.UpdateUserAdminParams{ Login: u.Login, Admin: admin, }) if err != nil { fmt.Printf("ERROR: Failed to make user %s (%s).\n", doing, err) return nil } fmt.Printf("User is now %s.\n", doing) return nil } // ActionUserAdmin handles the `USER ADMIN` command. func ActionUserAdmin(_ *dclish.Command) error { // TODO: implement return nil func ActionUserAdmin(cmd *dclish.Command) error { return actionUserAdmin(cmd, 1, "an admin") } // ActionUserNoadmin handles the `USER NOADMIN` command. func ActionUserNoadmin(_ *dclish.Command) error { // TODO: implement func ActionUserNoadmin(cmd *dclish.Command) error { return actionUserAdmin(cmd, 0, "not an admin") } func actionUserMod(cmd *dclish.Command, mod int64, doing string) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } if u.Moderator == mod { fmt.Printf("User is already %s.\n", doing) return nil } ctx := storage.Context() err = this.Q.UpdateUserMod(ctx, storage.UpdateUserModParams{ Login: u.Login, Moderator: mod, }) if err != nil { fmt.Printf("ERROR: Failed to make user %s (%s).\n", doing, err) return nil } fmt.Printf("User is now %s.\n", doing) return nil } // ActionUserMod handles the `USER MOD` command. func ActionUserMod(_ *dclish.Command) error { // TODO: implement return nil func ActionUserMod(cmd *dclish.Command) error { return actionUserMod(cmd, 1, "a moderator") } // ActionUserNomod handles the `USER NOMOD` command. func ActionUserNomod(_ *dclish.Command) error { // TODO: implement return nil func ActionUserNomod(cmd *dclish.Command) error { return actionUserMod(cmd, 0, "not a moderator") } // ActionSSH handles the `SSH` command. func ActionSSH(cmd *dclish.Command) error { fmt.Println(cmd.Description) fmt.Println(`\nThe following commands are available: fmt.Println(` The following commands are available: ADD DELETE LIST`) fmt.Println() return nil } // ActionSSHAdd handles the `SSH ADD` command. func ActionSSHAdd(_ *dclish.Command) error { // TODO: implement func ActionSSHAdd(cmd *dclish.Command) error { if this.User.Admin == 0 && len(cmd.Args) == 1 { fmt.Println("ERROR: You are not an admin.") return nil } // ActionSSHDelete handles the `SSH DELETE` command. func ActionSSHDelete(_ *dclish.Command) error { // TODO: implement login := this.User.Login if len(cmd.Args) == 1 { login = cmd.Args[0] } u, err := users.ValidExistingLogin(this.Q, login) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } sshkey, err := ask.GetLine("Enter ssh public key: ") if err != nil { fmt.Printf("ERROR: Failed to read ssh key (%s).\n", err) return nil } key.Add(login, sshkey) fmt.Println("Key is added.") return nil } // ActionSSHList handles the `SSH LIST` command. func ActionSSHList(_ *dclish.Command) error { // TODO: implement func ActionSSHList(cmd *dclish.Command) error { if this.User.Admin == 0 && len(cmd.Args) == 1 { fmt.Println("ERROR: You are not an admin.") return nil } login := this.User.Login if len(cmd.Args) == 1 { login = cmd.Args[0] } u, err := users.ValidExistingLogin(this.Q, login) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } keys, err := key.List(login) if err != nil { fmt.Printf("ERROR: Problem listing keys (%s).\n", err) return nil } fmt.Printf("The %d keys:\n %s\n", len(keys), strings.Join(keys, "\n ")) return nil } // ActionSSHDelete handles the `SSH DELETE` command. func ActionSSHDelete(cmd *dclish.Command) error { if this.User.Admin == 0 && len(cmd.Args) == 1 { fmt.Println("ERROR: You are not an admin.") return nil } login := this.User.Login if len(cmd.Args) == 1 { login = cmd.Args[0] } u, err := users.ValidExistingLogin(this.Q, login) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } keys, err := key.List(login) if err != nil { fmt.Printf("ERROR: Problem listing keys (%s).\n", err) return nil } if len(keys) == 0 { fmt.Println("No keys to delete.") return nil } choice, err := ask.Choose("Choose a key to delete:", keys) if err != nil { fmt.Printf("ERROR: Problem choosing key (%s).\n", err) return nil } if choice < 0 { fmt.Println("Aborted.") return nil } err = key.Delete(keys[choice]) if err != nil { fmt.Printf("ERROR: Problem deleting key (%s).\n", err) return nil } fmt.Println("Key deleted.") return nil } repl/command.go +27 −8 Original line number Diff line number Diff line Loading @@ -1141,6 +1141,10 @@ message.`, MaxArgs: 1, Action: ActionUserNoadmin, }, "LIST": { Description: ` Lists all the users. Must be an admin.`, Action: ActionUserList, }, "MOD": { Description: ` Makes a user an mod.`, MinArgs: 1, Loading @@ -1160,21 +1164,36 @@ message.`, Action: ActionSSH, Commands: dclish.Commands{ "ADD": { Description: ` Adds an ssh key for a user. Description: ` Adds an ssh key for a user. Only an admin can add an ssh key for another user. Prompts the user for an ssh key and then adds it. Prompts the user for an ssh key and then adds it.`, Format: SSH ADD [login]`, Action: ActionSSHAdd, MaxArgs: 1, }, "DELETE": { Description: ` Removes an ssh key for a user. Description: ` Removes an ssh key for a user. Only an admin can remove an ssh key for another user. The user is given a list of current ssh keys and is asked which to remove.`, remove. Format: SSH DELETE [login]`, Action: ActionSSHDelete, MaxArgs: 1, }, "LIST": { Description: ` Prints a list of ssh keys for the user.`, Description: ` Prints a list of ssh keys for a user. Only an admin can list ssh keys for another user. Format: SSH LIST [login]`, Action: ActionSSHList, MaxArgs: 1, }, }, }, Loading Loading
NOTES.md +2 −2 Original line number Diff line number Diff line Loading @@ -35,11 +35,11 @@ Switch between MAIL and BULLETIN modes? MAIL commands are documented * Database * trigger to limit values for 'visibility'? * Add commands: * A way to add / delete ssh keys. * ~~A way to add / delete ssh keys.~~ * A way to manage files? * Commands for a local mail system? * Commands to connect to Mattermost or mastodon? * Commands to manage users. * ~~Commands to manage users.~~ * Handle MARK for SELECT and DIRECTORY. Done: Loading
ask/ask.go +23 −0 Original line number Diff line number Diff line Loading @@ -5,8 +5,10 @@ getting a line of text, getting a choice from a liat and other things. package ask import ( "errors" "fmt" "os" "strconv" "github.com/chzyer/readline" ) Loading @@ -29,3 +31,24 @@ func GetLine(prompt string) (string, error) { line, err := rl.Readline() return line, err } // Choose presents a list and asks a user to choose one. func Choose(prompt string, choices []string) (int, error) { fmt.Println(prompt) for i := range choices { fmt.Printf(" %d. %s\n", i+1, choices[i]) } response, err := GetLine("Choose a number or enter q to abort: ") if err != nil { return -1, err } choice, err := strconv.Atoi(response) if err != nil { return -1, nil } choice-- if choice < 0 || choice >= len(choices) { return -1, errors.New("Choice out of bounds") } return choice, nil }
key/key.go +151 −4 Original line number Diff line number Diff line Loading @@ -2,24 +2,38 @@ package key import ( "bufio" "bytes" "errors" "fmt" "io" "os" "path" "strings" "github.com/adrg/xdg" "golang.org/x/crypto/ssh" "golang.org/x/sys/unix" ) var keytemplate = `command="%s -u %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s\n` var keytemplate = `command="%s -u %s",no-port-forwarding,no-X11-forwarding,no-agent-forwarding %s` // Add adds an ssh key to the `authorized_keys` file. func Add(login, public string) error { bulletin, err := os.Executable() // Parse and verify the key. public = strings.TrimSpace(public) theKey, _, _, _, err := ssh.ParseAuthorizedKey([]byte(public)) if err != nil { return err } keyline := fmt.Sprintf(keytemplate, bulletin, login, public) // Find the bulletin binary. bulletin, err := os.Executable() if err != nil { return err } // File system management. sshdir := path.Join(xdg.Home, ".ssh") err = os.MkdirAll(sshdir, 0700) if err != nil { Loading @@ -27,11 +41,28 @@ func Add(login, public string) error { } keyfile := path.Join(sshdir, "authorized_keys") f, err := os.OpenFile(keyfile, os.O_APPEND|os.O_WRONLY|os.O_CREATE, 0600) // Open and lock the authorized_keys file. f, err := os.OpenFile(keyfile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } defer f.Close() if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { return err } defer unix.Flock(int(f.Fd()), unix.LOCK_UN) // unlock after we're done // Check for duplicates. keycontent, err := io.ReadAll(f) if err != nil { return err } if bytes.Contains(keycontent, ssh.MarshalAuthorizedKey(theKey)) { return errors.New("key already exists") } // Generate and write the key. keyline := fmt.Sprintf(keytemplate, bulletin, login, string(ssh.MarshalAuthorizedKey(theKey))) n, err := f.WriteString(keyline) if err != nil { return err Loading @@ -43,3 +74,119 @@ func Add(login, public string) error { return nil } // List returns a list of ssh keys for this user. func List(login string) ([]string, error) { keys := []string{} // Find the bulletin binary. bulletin, err := os.Executable() if err != nil { return keys, err } // File system management. sshdir := path.Join(xdg.Home, ".ssh") err = os.MkdirAll(sshdir, 0700) if err != nil { return keys, err } keyfile := path.Join(sshdir, "authorized_keys") // Open the authorized_keys file. f, err := os.OpenFile(keyfile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return keys, err } defer f.Close() // look for lines. scanner := bufio.NewScanner(f) for scanner.Scan() { keyline := bytes.TrimSpace(scanner.Bytes()) if len(keyline) == 0 { continue } public, _, options, _, err := ssh.ParseAuthorizedKey([]byte(keyline)) if err != nil { return keys, err } for i := range options { opts := strings.SplitN(options[i], "=", 2) if len(opts) != 2 || opts[0] != "command" { continue } cmd := strings.Split(strings.Trim(opts[1], "\" "), " ") if len(cmd) != 3 { return keys, fmt.Errorf("Unexpected command in authorized keys file (%s)", opts[1]) } if cmd[0] != bulletin { return keys, fmt.Errorf("Unexpected bulletin in authorized keys file (%s)", opts[1]) } if cmd[1] != "-u" { return keys, fmt.Errorf("Unexpected flag in authorized keys file (%s)", opts[1]) } if cmd[2] == login { keys = append(keys, strings.Trim(string(ssh.MarshalAuthorizedKey(public)), "\n")) } break } } return keys, nil } // Delete removes the key. func Delete(public string) error { keys := []string{} // Parse and verify the key. public = strings.TrimSpace(public) doomedRaw, _, _, _, err := ssh.ParseAuthorizedKey([]byte(public)) if err != nil { return err } // File system management. sshdir := path.Join(xdg.Home, ".ssh") err = os.MkdirAll(sshdir, 0700) if err != nil { return err } keyfile := path.Join(sshdir, "authorized_keys") // Open the authorized_keys file. f, err := os.OpenFile(keyfile, os.O_RDWR|os.O_CREATE, 0600) if err != nil { return err } defer f.Close() if err := unix.Flock(int(f.Fd()), unix.LOCK_EX); err != nil { return err } defer unix.Flock(int(f.Fd()), unix.LOCK_UN) // unlock after we're done // look for lines. doomed := doomedRaw.Marshal() scanner := bufio.NewScanner(f) for scanner.Scan() { keyline := bytes.TrimSpace(scanner.Bytes()) if len(keyline) == 0 { continue } potential, _, _, _, err := ssh.ParseAuthorizedKey(keyline) if err != nil { return err } if bytes.Compare(potential.Marshal(), doomed) != 0 { keys = append(keys, string(keyline)) } } if _, err := f.Seek(0, os.SEEK_SET); err != nil { return fmt.Errorf("seek: %w", err) } f.Truncate(0) f.WriteString(strings.Join(keys, "\n") + "\n") return nil }
repl/accounts.go +226 −31 Original line number Diff line number Diff line Loading @@ -2,8 +2,14 @@ package repl import ( "fmt" "strings" "git.lyda.ie/kevin/bulletin/ask" "git.lyda.ie/kevin/bulletin/dclish" "git.lyda.ie/kevin/bulletin/key" "git.lyda.ie/kevin/bulletin/storage" "git.lyda.ie/kevin/bulletin/this" "git.lyda.ie/kevin/bulletin/users" ) // ActionUser handles the `USER` command. Loading @@ -12,83 +18,272 @@ func ActionUser(cmd *dclish.Command) error { fmt.Println(` The following commands are available: ADD ADMIN DELETE DISABLE ENABLE MOD NOADMIN NOMOD`) ADD ADMIN DELETE DISABLE ENABLE LIST MOD NOADMIN NOMOD`) fmt.Println() return nil } // ActionUserAdd handles the `USER ADD` command. func ActionUserAdd(_ *dclish.Command) error { // TODO: implement func ActionUserAdd(cmd *dclish.Command) error { login := strings.ToUpper(cmd.Args[0]) err := users.ValidLogin(login) if err != nil { fmt.Printf("ERROR: %s.\n", err) return nil } return nil } // ActionUserList handles the `USER LIST` command. func ActionUserList(_ *dclish.Command) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } ctx := storage.Context() userlist, err := this.Q.ListUsers(ctx) if err != nil { fmt.Printf("ERROR: Failed to list users (%s).\n", err) return nil } // TODO: nicer output for user. for _, u := range userlist { fmt.Printf("%s\n", u) } return nil } // ActionUserDelete handles the `USER DELETE` command. func ActionUserDelete(_ *dclish.Command) error { // TODO: implement func ActionUserDelete(cmd *dclish.Command) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } ctx := storage.Context() err = this.Q.DeleteUser(ctx, u.Login) if err != nil { fmt.Printf("ERROR: Failed to delete user (%s).\n", err) return nil } fmt.Println("User deleted.") return nil } // ActionUserEnable handles the `USER ENABLE` command. func ActionUserEnable(_ *dclish.Command) error { // TODO: implement func actionUserEnable(cmd *dclish.Command, disabled int64, doing string) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } if u.Disabled == disabled { fmt.Printf("User already %sd.\n", doing) return nil } ctx := storage.Context() err = this.Q.UpdateUserDisabled(ctx, storage.UpdateUserDisabledParams{ Login: u.Login, Disabled: disabled, }) if err != nil { fmt.Printf("ERROR: Failed to %s user (%s).\n", doing, err) return nil } fmt.Printf("User %sd.\n", doing) return nil } // ActionUserEnable handles the `USER ENABLE` command. func ActionUserEnable(cmd *dclish.Command) error { return actionUserEnable(cmd, 0, "enable") } // ActionUserDisable handles the `USER DISABLE` command. func ActionUserDisable(_ *dclish.Command) error { // TODO: implement func ActionUserDisable(cmd *dclish.Command) error { return actionUserEnable(cmd, 1, "disable") } func actionUserAdmin(cmd *dclish.Command, admin int64, doing string) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } if u.Admin == admin { fmt.Printf("User is already %s.\n", doing) return nil } ctx := storage.Context() err = this.Q.UpdateUserAdmin(ctx, storage.UpdateUserAdminParams{ Login: u.Login, Admin: admin, }) if err != nil { fmt.Printf("ERROR: Failed to make user %s (%s).\n", doing, err) return nil } fmt.Printf("User is now %s.\n", doing) return nil } // ActionUserAdmin handles the `USER ADMIN` command. func ActionUserAdmin(_ *dclish.Command) error { // TODO: implement return nil func ActionUserAdmin(cmd *dclish.Command) error { return actionUserAdmin(cmd, 1, "an admin") } // ActionUserNoadmin handles the `USER NOADMIN` command. func ActionUserNoadmin(_ *dclish.Command) error { // TODO: implement func ActionUserNoadmin(cmd *dclish.Command) error { return actionUserAdmin(cmd, 0, "not an admin") } func actionUserMod(cmd *dclish.Command, mod int64, doing string) error { if this.User.Admin == 0 { fmt.Println("ERROR: You are not an admin.") return nil } u, err := users.ValidExistingLogin(this.Q, cmd.Args[0]) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } if u.Moderator == mod { fmt.Printf("User is already %s.\n", doing) return nil } ctx := storage.Context() err = this.Q.UpdateUserMod(ctx, storage.UpdateUserModParams{ Login: u.Login, Moderator: mod, }) if err != nil { fmt.Printf("ERROR: Failed to make user %s (%s).\n", doing, err) return nil } fmt.Printf("User is now %s.\n", doing) return nil } // ActionUserMod handles the `USER MOD` command. func ActionUserMod(_ *dclish.Command) error { // TODO: implement return nil func ActionUserMod(cmd *dclish.Command) error { return actionUserMod(cmd, 1, "a moderator") } // ActionUserNomod handles the `USER NOMOD` command. func ActionUserNomod(_ *dclish.Command) error { // TODO: implement return nil func ActionUserNomod(cmd *dclish.Command) error { return actionUserMod(cmd, 0, "not a moderator") } // ActionSSH handles the `SSH` command. func ActionSSH(cmd *dclish.Command) error { fmt.Println(cmd.Description) fmt.Println(`\nThe following commands are available: fmt.Println(` The following commands are available: ADD DELETE LIST`) fmt.Println() return nil } // ActionSSHAdd handles the `SSH ADD` command. func ActionSSHAdd(_ *dclish.Command) error { // TODO: implement func ActionSSHAdd(cmd *dclish.Command) error { if this.User.Admin == 0 && len(cmd.Args) == 1 { fmt.Println("ERROR: You are not an admin.") return nil } // ActionSSHDelete handles the `SSH DELETE` command. func ActionSSHDelete(_ *dclish.Command) error { // TODO: implement login := this.User.Login if len(cmd.Args) == 1 { login = cmd.Args[0] } u, err := users.ValidExistingLogin(this.Q, login) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } sshkey, err := ask.GetLine("Enter ssh public key: ") if err != nil { fmt.Printf("ERROR: Failed to read ssh key (%s).\n", err) return nil } key.Add(login, sshkey) fmt.Println("Key is added.") return nil } // ActionSSHList handles the `SSH LIST` command. func ActionSSHList(_ *dclish.Command) error { // TODO: implement func ActionSSHList(cmd *dclish.Command) error { if this.User.Admin == 0 && len(cmd.Args) == 1 { fmt.Println("ERROR: You are not an admin.") return nil } login := this.User.Login if len(cmd.Args) == 1 { login = cmd.Args[0] } u, err := users.ValidExistingLogin(this.Q, login) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } keys, err := key.List(login) if err != nil { fmt.Printf("ERROR: Problem listing keys (%s).\n", err) return nil } fmt.Printf("The %d keys:\n %s\n", len(keys), strings.Join(keys, "\n ")) return nil } // ActionSSHDelete handles the `SSH DELETE` command. func ActionSSHDelete(cmd *dclish.Command) error { if this.User.Admin == 0 && len(cmd.Args) == 1 { fmt.Println("ERROR: You are not an admin.") return nil } login := this.User.Login if len(cmd.Args) == 1 { login = cmd.Args[0] } u, err := users.ValidExistingLogin(this.Q, login) if err != nil || u.Login == "" { fmt.Println("ERROR: User not found.") return nil } keys, err := key.List(login) if err != nil { fmt.Printf("ERROR: Problem listing keys (%s).\n", err) return nil } if len(keys) == 0 { fmt.Println("No keys to delete.") return nil } choice, err := ask.Choose("Choose a key to delete:", keys) if err != nil { fmt.Printf("ERROR: Problem choosing key (%s).\n", err) return nil } if choice < 0 { fmt.Println("Aborted.") return nil } err = key.Delete(keys[choice]) if err != nil { fmt.Printf("ERROR: Problem deleting key (%s).\n", err) return nil } fmt.Println("Key deleted.") return nil }
repl/command.go +27 −8 Original line number Diff line number Diff line Loading @@ -1141,6 +1141,10 @@ message.`, MaxArgs: 1, Action: ActionUserNoadmin, }, "LIST": { Description: ` Lists all the users. Must be an admin.`, Action: ActionUserList, }, "MOD": { Description: ` Makes a user an mod.`, MinArgs: 1, Loading @@ -1160,21 +1164,36 @@ message.`, Action: ActionSSH, Commands: dclish.Commands{ "ADD": { Description: ` Adds an ssh key for a user. Description: ` Adds an ssh key for a user. Only an admin can add an ssh key for another user. Prompts the user for an ssh key and then adds it. Prompts the user for an ssh key and then adds it.`, Format: SSH ADD [login]`, Action: ActionSSHAdd, MaxArgs: 1, }, "DELETE": { Description: ` Removes an ssh key for a user. Description: ` Removes an ssh key for a user. Only an admin can remove an ssh key for another user. The user is given a list of current ssh keys and is asked which to remove.`, remove. Format: SSH DELETE [login]`, Action: ActionSSHDelete, MaxArgs: 1, }, "LIST": { Description: ` Prints a list of ssh keys for the user.`, Description: ` Prints a list of ssh keys for a user. Only an admin can list ssh keys for another user. Format: SSH LIST [login]`, Action: ActionSSHList, MaxArgs: 1, }, }, }, Loading