// Copyright (c) 2020, Andreas Schneider // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // * Redistributions of source code must retain the above copyright // notice, this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright // notice, this list of conditions and the following disclaimer in the // documentation and/or other materials provided with the distribution. // * Neither the name of the nor the // names of its contributors may be used to endorse or promote products // derived from this software without specific prior written permission. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND // ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED // WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE // DISCLAIMED. IN NO EVENT SHALL BE LIABLE FOR ANY // DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES // (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; // LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND // ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT // (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS // SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. package main import ( "fmt" "os" "golang.org/x/crypto/bcrypt" ) type CmdUser struct { CmdList CmdUserList `cmd:"" name:"list" help:"List all users."` CmdAdd CmdUserAdd `cmd:"" name:"add" help:"Add a user."` CmdUpdate CmdUserUpdate `cmd:"" name:"update" help:"Update a user."` CmdDelete CmdUserDelete `cmd:"" name:"delete" help:"Delete a user."` } type CmdUserList struct{} func (cmd CmdUserList) Run(app *app) error { users, err := app.userStore.GetUsers() if err != nil { return err } for _, user := range users { fmt.Printf("* %s (%s)\n", user.Username, user.Role) } return nil } type CmdUserAdd struct { Username string `arg:"" name:"username" help:"The username to be added."` PasswordParam Role GlobalRole `name:"role" default:"user" help:"Role of the user. 'admin' or ' user'"` } func (cmd CmdUserAdd) Run(app *app) error { switch cmd.Role { case GlobalRoleUser: case GlobalRoleAdmin: default: return fmt.Errorf("invalid user role") } password, err := cmd.acquirePassword() if err != nil { return fmt.Errorf("cannot acquire password for user: %w", err) } user := User{ Username: cmd.Username, Password: password, Role: cmd.Role, } return app.userStore.AddUser(user) } type CmdUserUpdate struct { Username string `arg:"" name:"username" help:"The username of the user to be updated."` Password string `name:"password" help:"Update the password, if set."` Role GlobalRole `name:"role" default:"user" help:"Update the role, if set. 'admin' or ' user'"` } func (cmd CmdUserUpdate) Run(app *app) error { user, err := app.userStore.GetUser(cmd.Username) if err != nil { return err } changed := false if cmd.Role != "" && cmd.Role != user.Role { switch cmd.Role { case GlobalRoleUser: case GlobalRoleAdmin: default: return fmt.Errorf("invalid user role") } user.Role = cmd.Role changed = true } if cmd.Password != "" { hash, err := bcrypt.GenerateFromPassword([]byte(cmd.Password), 0) if err != nil { return fmt.Errorf("cannot hash password: %w", err) } user.Password = string(hash) changed = true } if !changed { // Nothing changed. Nothing to write. Not different from a successful write to the user. return nil } return app.userStore.UpdateUser(user) } type CmdUserDelete struct { Username string `arg:"" name:"username" help:"The username of the user to be deleted."` } func (cmd CmdUserDelete) Run(app *app) error { if err := app.userStore.RemoveUser(cmd.Username); err != nil { return fmt.Errorf("cannot remove user: %w", err) } sharesByUser, err := app.shareStore.FindSharesByUser(cmd.Username) if err != nil { return fmt.Errorf("cannot get shares of user: %w", err) } allSuccessful := true for _, userShare := range sharesByUser { if err := app.shareStore.RemoveShare(userShare.UUID); err != nil { fmt.Fprintf(os.Stderr, "User %q cannot be removed from Share %q: %v\n", cmd.Username, userShare.UUID.String(), err) allSuccessful = false } } if !allSuccessful { return fmt.Errorf("could not remove user from all shares") } return nil }