From 219eecbfda778f70d622471fa1d2a4dc72b3ea87 Mon Sep 17 00:00:00 2001 From: Aaron Lindsay Date: Tue, 27 Aug 2013 00:03:38 -0400 Subject: [PATCH] Finish core funtionality for server user-administration tool --- server/admin/users.go | 26 ++++++-- server/admin_rpc.go | 61 +++++++++++++----- server/{server => }/database.go | 110 +++++++++++++++++++++++++++++--- server/server/server.go | 6 +- 4 files changed, 166 insertions(+), 37 deletions(-) rename server/{server => }/database.go (58%) diff --git a/server/admin/users.go b/server/admin/users.go index f694270..897776c 100644 --- a/server/admin/users.go +++ b/server/admin/users.go @@ -7,7 +7,7 @@ import ( "fmt" "os" "strconv" - "reflect" + "net/rpc" ) type boolIsSetFlag struct { @@ -66,10 +66,13 @@ func UserAdd(args []string) { user.Username = flags.Arg(0) user.PWHash = server.HashPassword(passwordOne) - fmt.Println(user) i := 99 err = RPCCall("UserModifier.AddUser", user, &i) if err != nil { + if _, ok := err.(rpc.ServerError); ok && err.Error() == server.DuplicateUsernameErr.Error() { + fmt.Println("Error: "+err.Error()) + return + } panic(err) } } @@ -83,10 +86,13 @@ func UserDel(args []string) { user := new(server.User) user.Username = args[0] - fmt.Println(user) i := 99 err := RPCCall("UserModifier.RemoveUser", user, &i) if err != nil { + if _, ok := err.(rpc.ServerError); ok && err.Error() == server.NoUserErr.Error() { + fmt.Println("Error: "+err.Error()) + return + } panic(err) } } @@ -106,8 +112,6 @@ func UserMod(args []string) { flags.BoolVar(&rpcargs.UpdateLogin, "l", false, "Change the user's username (short version)") flags.Parse(args) - //set the UpdateAdmin flag based on whether it was present on the command-line - if flags.NArg() != 1 { fmt.Println("Error: please supply a username (and only one)") os.Exit(1) @@ -136,6 +140,7 @@ func UserMod(args []string) { rpcargs.Updated.PWHash = server.HashPassword(passwordOne) } + //set the UpdateRole flag based on whether it was present on the command-line rpcargs.UpdateRole = admin.IsSet if admin.Value { rpcargs.Updated.Role = server.ADMIN @@ -143,11 +148,18 @@ func UserMod(args []string) { rpcargs.Updated.Role = server.NORMAL } - fmt.Println(rpcargs) + if !rpcargs.UpdateRole && !rpcargs.UpdateLogin && !rpcargs.UpdatePassword { + fmt.Println("What exactly are you modifying again?") + return + } + i := 99 err := RPCCall("UserModifier.ModifyUser", rpcargs, &i) if err != nil { - fmt.Println(reflect.TypeOf(err)) + if _, ok := err.(rpc.ServerError); ok && err.Error() == server.NoUserErr.Error() { + fmt.Println("Error: "+err.Error()) + return + } panic(err) } } diff --git a/server/admin_rpc.go b/server/admin_rpc.go index 20da3aa..26d275b 100644 --- a/server/admin_rpc.go +++ b/server/admin_rpc.go @@ -1,13 +1,14 @@ package server import ( - "fmt" "net" "net/http" "net/rpc" ) -type UserModifier int +type UserModifier struct { + adb *AsinkDB +} type UserModifierArgs struct { Current *User @@ -18,32 +19,58 @@ type UserModifierArgs struct { } func (u *UserModifier) AddUser(user *User, result *int) error { - fmt.Println("adding user: ", user) - ret := 0 - result = &ret - return nil + err := u.adb.DatabaseAddUser(user) + if err != nil { + *result = 1 + } else { + *result = 0 + } + return err } func (u *UserModifier) ModifyUser(args *UserModifierArgs, result *int) error { - fmt.Println("modifying user: ", args) - fmt.Println("from: ", args.Current) - fmt.Println("to: ", args.Updated) - ret := 0 - result = &ret + currentUser, err := u.adb.DatabaseGetUser(args.Current.Username) + if err != nil { + *result = 1 + return err + } + + if args.UpdateLogin { + currentUser.Username = args.Updated.Username + } + if args.UpdateRole { + currentUser.Role = args.Updated.Role + } + if args.UpdatePassword { + currentUser.PWHash = args.Updated.PWHash + } + + err = u.adb.DatabaseUpdateUser(currentUser) + if err != nil { + *result = 1 + return err + } + + *result = 0 return nil } func (u *UserModifier) RemoveUser(user *User, result *int) error { - fmt.Println("removing user: ", user) - ret := 0 - result = &ret - return nil + err := u.adb.DatabaseDeleteUser(user) + if err != nil { + *result = 1 + } else { + *result = 0 + } + return err } -func StartRPC(townDown chan int) { - defer func() { townDown <- 0 }() //the main thread waits for this to ensure the socket is closed +func StartRPC(tornDown chan int, adb *AsinkDB) { + defer func() { tornDown <- 0 }() //the main thread waits for this to ensure the socket is closed usermod := new(UserModifier) + usermod.adb = adb + rpc.Register(usermod) rpc.HandleHTTP() l, err := net.Listen("unix", "/tmp/asink.sock") diff --git a/server/server/database.go b/server/database.go similarity index 58% rename from server/server/database.go rename to server/database.go index a92742a..638be63 100644 --- a/server/server/database.go +++ b/server/database.go @@ -1,8 +1,8 @@ -package main +package server import ( "asink" - "asink/server" + "errors" "database/sql" _ "github.com/mattn/go-sqlite3" "sync" @@ -13,6 +13,9 @@ type AsinkDB struct { lock sync.Mutex } +var DuplicateUsernameErr = errors.New("Username already exists") +var NoUserErr = errors.New("User doesn't exist") + func GetAndInitDB() (*AsinkDB, error) { dbLocation := "asink-server.db" //TODO make me configurable @@ -46,7 +49,7 @@ func GetAndInitDB() (*AsinkDB, error) { } if !rows.Next() { //if this is false, it means no rows were returned - tx.Exec("CREATE TABLE user (id INTEGER PRIMARY KEY ASC, username TEXT, pwhash TEXT, role INTEGER);") + tx.Exec("CREATE TABLE users (id INTEGER PRIMARY KEY ASC, username TEXT, pwhash TEXT, role INTEGER);") } else { rows.Close() } @@ -116,7 +119,7 @@ func (adb *AsinkDB) DatabaseRetrieveEvents(firstId uint64, maxEvents uint) (even return events, nil } -func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) { +func (adb *AsinkDB) DatabaseAddUser(u *User) (err error) { adb.lock.Lock() tx, err := adb.db.Begin() if err != nil { @@ -131,11 +134,24 @@ func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) { adb.lock.Unlock() }() - result, err := tx.Exec("INSERT INTO users (username, pwhash, role) VALUES (?,?);", u.Username, u.PWHash, u.Role) + //make sure the username we're switching to doesn't already exist in the database + existingUsername := "" + row := tx.QueryRow("SELECT username FROM users WHERE username == ?;", u.Username) + err = row.Scan(&existingUsername) + switch { + case err == sql.ErrNoRows: + //keep going + case err != nil: + return err + default: + return DuplicateUsernameErr + } + + result, err := tx.Exec("INSERT INTO users (username, pwhash, role) VALUES (?,?,?);", u.Username, u.PWHash, u.Role) if err != nil { return err } - id, err := result.LastInsertId() + u.Id, err = result.LastInsertId() if err != nil { return err } @@ -144,23 +160,63 @@ func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) { return err } - u.Id = id return nil } -func (adb *AsinkDB) DatabaseGetUser(username string) (user *server.User, err error) { +//set attributes for the user with the same Id as *u +func (adb *AsinkDB) DatabaseUpdateUser(u *User) (err error) { + adb.lock.Lock() + tx, err := adb.db.Begin() + if err != nil { + return err + } + + //make sure the transaction gets rolled back on error, and the database gets unlocked + defer func() { + if err != nil { + tx.Rollback() + } + adb.lock.Unlock() + }() + + //make sure the username we're switching to doesn't already exist in the database + existingUsername := "" + row := tx.QueryRow("SELECT username FROM users WHERE username == ? AND id != ?;", u.Username, u.Id) + err = row.Scan(&existingUsername) + switch { + case err == sql.ErrNoRows: + //keep going + case err != nil: + return err + default: + return DuplicateUsernameErr + } + + _, err = tx.Exec("UPDATE users SET username=?, pwhash=?, role=? WHERE id=?;", u.Username, u.PWHash, u.Role, u.Id) + if err != nil { + return err + } + err = tx.Commit() + if err != nil { + return err + } + + return nil +} + +func (adb *AsinkDB) DatabaseGetUser(username string) (user *User, err error) { adb.lock.Lock() //make sure the database gets unlocked defer adb.lock.Unlock() row := adb.db.QueryRow("SELECT id, username, pwhash, role FROM users WHERE username == ?;", username) - user = new(server.User) + user = new(User) err = row.Scan(&user.Id, &user.Username, &user.PWHash, &user.Role) switch { case err == sql.ErrNoRows: - return nil, nil + return nil, NoUserErr case err != nil: return nil, err default: @@ -168,4 +224,38 @@ func (adb *AsinkDB) DatabaseGetUser(username string) (user *server.User, err err } } +func (adb *AsinkDB) DatabaseDeleteUser(u *User) (err error) { + adb.lock.Lock() + tx, err := adb.db.Begin() + if err != nil { + return err + } + //make sure the transaction gets rolled back on error, and the database gets unlocked + defer func() { + if err != nil { + tx.Rollback() + } + adb.lock.Unlock() + }() + + res, err := tx.Exec("DELETE FROM users WHERE username=?;", u.Username) + if err != nil { + return err + } + rows, err := res.RowsAffected() + if err != nil { + return err + } + if rows == 0 { + return NoUserErr + } else if rows > 1 { + return errors.New("Error: attempting to delete user by username, but more than row will be affected: " + u.Username) + } + err = tx.Commit() + if err != nil { + return err + } + + return nil +} diff --git a/server/server/server.go b/server/server/server.go index 2549625..f0469a9 100644 --- a/server/server/server.go +++ b/server/server/server.go @@ -18,7 +18,7 @@ import ( //global variables var eventsRegexp *regexp.Regexp var port int = 8080 -var adb *AsinkDB +var adb *server.AsinkDB func init() { var err error @@ -29,7 +29,7 @@ func init() { eventsRegexp = regexp.MustCompile("^/events/([0-9]+)$") - adb, err = GetAndInitDB() + adb, err = server.GetAndInitDB() if err != nil { panic(err) } @@ -39,7 +39,7 @@ func main() { flag.Parse() rpcTornDown := make(chan int) - go server.StartRPC(rpcTornDown) + go server.StartRPC(rpcTornDown, adb) http.HandleFunc("/", rootHandler) http.HandleFunc("/events", eventHandler)