Finish core funtionality for server user-administration tool

This commit is contained in:
Aaron Lindsay 2013-08-27 00:03:38 -04:00
parent 4d7d82ed94
commit 219eecbfda
4 changed files with 166 additions and 37 deletions

View File

@ -7,7 +7,7 @@ import (
"fmt" "fmt"
"os" "os"
"strconv" "strconv"
"reflect" "net/rpc"
) )
type boolIsSetFlag struct { type boolIsSetFlag struct {
@ -66,10 +66,13 @@ func UserAdd(args []string) {
user.Username = flags.Arg(0) user.Username = flags.Arg(0)
user.PWHash = server.HashPassword(passwordOne) user.PWHash = server.HashPassword(passwordOne)
fmt.Println(user)
i := 99 i := 99
err = RPCCall("UserModifier.AddUser", user, &i) err = RPCCall("UserModifier.AddUser", user, &i)
if err != nil { if err != nil {
if _, ok := err.(rpc.ServerError); ok && err.Error() == server.DuplicateUsernameErr.Error() {
fmt.Println("Error: "+err.Error())
return
}
panic(err) panic(err)
} }
} }
@ -83,10 +86,13 @@ func UserDel(args []string) {
user := new(server.User) user := new(server.User)
user.Username = args[0] user.Username = args[0]
fmt.Println(user)
i := 99 i := 99
err := RPCCall("UserModifier.RemoveUser", user, &i) err := RPCCall("UserModifier.RemoveUser", user, &i)
if err != nil { if err != nil {
if _, ok := err.(rpc.ServerError); ok && err.Error() == server.NoUserErr.Error() {
fmt.Println("Error: "+err.Error())
return
}
panic(err) panic(err)
} }
} }
@ -106,8 +112,6 @@ func UserMod(args []string) {
flags.BoolVar(&rpcargs.UpdateLogin, "l", false, "Change the user's username (short version)") flags.BoolVar(&rpcargs.UpdateLogin, "l", false, "Change the user's username (short version)")
flags.Parse(args) flags.Parse(args)
//set the UpdateAdmin flag based on whether it was present on the command-line
if flags.NArg() != 1 { if flags.NArg() != 1 {
fmt.Println("Error: please supply a username (and only one)") fmt.Println("Error: please supply a username (and only one)")
os.Exit(1) os.Exit(1)
@ -136,6 +140,7 @@ func UserMod(args []string) {
rpcargs.Updated.PWHash = server.HashPassword(passwordOne) rpcargs.Updated.PWHash = server.HashPassword(passwordOne)
} }
//set the UpdateRole flag based on whether it was present on the command-line
rpcargs.UpdateRole = admin.IsSet rpcargs.UpdateRole = admin.IsSet
if admin.Value { if admin.Value {
rpcargs.Updated.Role = server.ADMIN rpcargs.Updated.Role = server.ADMIN
@ -143,11 +148,18 @@ func UserMod(args []string) {
rpcargs.Updated.Role = server.NORMAL 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 i := 99
err := RPCCall("UserModifier.ModifyUser", rpcargs, &i) err := RPCCall("UserModifier.ModifyUser", rpcargs, &i)
if err != nil { 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) panic(err)
} }
} }

View File

@ -1,13 +1,14 @@
package server package server
import ( import (
"fmt"
"net" "net"
"net/http" "net/http"
"net/rpc" "net/rpc"
) )
type UserModifier int type UserModifier struct {
adb *AsinkDB
}
type UserModifierArgs struct { type UserModifierArgs struct {
Current *User Current *User
@ -18,32 +19,58 @@ type UserModifierArgs struct {
} }
func (u *UserModifier) AddUser(user *User, result *int) error { func (u *UserModifier) AddUser(user *User, result *int) error {
fmt.Println("adding user: ", user) err := u.adb.DatabaseAddUser(user)
ret := 0 if err != nil {
result = &ret *result = 1
return nil } else {
*result = 0
}
return err
} }
func (u *UserModifier) ModifyUser(args *UserModifierArgs, result *int) error { func (u *UserModifier) ModifyUser(args *UserModifierArgs, result *int) error {
fmt.Println("modifying user: ", args) currentUser, err := u.adb.DatabaseGetUser(args.Current.Username)
fmt.Println("from: ", args.Current) if err != nil {
fmt.Println("to: ", args.Updated) *result = 1
ret := 0 return err
result = &ret }
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 return nil
} }
func (u *UserModifier) RemoveUser(user *User, result *int) error { func (u *UserModifier) RemoveUser(user *User, result *int) error {
fmt.Println("removing user: ", user) err := u.adb.DatabaseDeleteUser(user)
ret := 0 if err != nil {
result = &ret *result = 1
return nil } else {
*result = 0
}
return err
} }
func StartRPC(townDown chan int) { func StartRPC(tornDown chan int, adb *AsinkDB) {
defer func() { townDown <- 0 }() //the main thread waits for this to ensure the socket is closed defer func() { tornDown <- 0 }() //the main thread waits for this to ensure the socket is closed
usermod := new(UserModifier) usermod := new(UserModifier)
usermod.adb = adb
rpc.Register(usermod) rpc.Register(usermod)
rpc.HandleHTTP() rpc.HandleHTTP()
l, err := net.Listen("unix", "/tmp/asink.sock") l, err := net.Listen("unix", "/tmp/asink.sock")

View File

@ -1,8 +1,8 @@
package main package server
import ( import (
"asink" "asink"
"asink/server" "errors"
"database/sql" "database/sql"
_ "github.com/mattn/go-sqlite3" _ "github.com/mattn/go-sqlite3"
"sync" "sync"
@ -13,6 +13,9 @@ type AsinkDB struct {
lock sync.Mutex lock sync.Mutex
} }
var DuplicateUsernameErr = errors.New("Username already exists")
var NoUserErr = errors.New("User doesn't exist")
func GetAndInitDB() (*AsinkDB, error) { func GetAndInitDB() (*AsinkDB, error) {
dbLocation := "asink-server.db" //TODO make me configurable dbLocation := "asink-server.db" //TODO make me configurable
@ -46,7 +49,7 @@ func GetAndInitDB() (*AsinkDB, error) {
} }
if !rows.Next() { if !rows.Next() {
//if this is false, it means no rows were returned //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 { } else {
rows.Close() rows.Close()
} }
@ -116,7 +119,7 @@ func (adb *AsinkDB) DatabaseRetrieveEvents(firstId uint64, maxEvents uint) (even
return events, nil return events, nil
} }
func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) { func (adb *AsinkDB) DatabaseAddUser(u *User) (err error) {
adb.lock.Lock() adb.lock.Lock()
tx, err := adb.db.Begin() tx, err := adb.db.Begin()
if err != nil { if err != nil {
@ -131,11 +134,24 @@ func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) {
adb.lock.Unlock() 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 { if err != nil {
return err return err
} }
id, err := result.LastInsertId() u.Id, err = result.LastInsertId()
if err != nil { if err != nil {
return err return err
} }
@ -144,23 +160,63 @@ func (adb *AsinkDB) DatabaseAddUser(u *server.User) (err error) {
return err return err
} }
u.Id = id
return nil 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() adb.lock.Lock()
//make sure the database gets unlocked //make sure the database gets unlocked
defer adb.lock.Unlock() defer adb.lock.Unlock()
row := adb.db.QueryRow("SELECT id, username, pwhash, role FROM users WHERE username == ?;", username) 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) err = row.Scan(&user.Id, &user.Username, &user.PWHash, &user.Role)
switch { switch {
case err == sql.ErrNoRows: case err == sql.ErrNoRows:
return nil, nil return nil, NoUserErr
case err != nil: case err != nil:
return nil, err return nil, err
default: 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
}

View File

@ -18,7 +18,7 @@ import (
//global variables //global variables
var eventsRegexp *regexp.Regexp var eventsRegexp *regexp.Regexp
var port int = 8080 var port int = 8080
var adb *AsinkDB var adb *server.AsinkDB
func init() { func init() {
var err error var err error
@ -29,7 +29,7 @@ func init() {
eventsRegexp = regexp.MustCompile("^/events/([0-9]+)$") eventsRegexp = regexp.MustCompile("^/events/([0-9]+)$")
adb, err = GetAndInitDB() adb, err = server.GetAndInitDB()
if err != nil { if err != nil {
panic(err) panic(err)
} }
@ -39,7 +39,7 @@ func main() {
flag.Parse() flag.Parse()
rpcTornDown := make(chan int) rpcTornDown := make(chan int)
go server.StartRPC(rpcTornDown) go server.StartRPC(rpcTornDown, adb)
http.HandleFunc("/", rootHandler) http.HandleFunc("/", rootHandler)
http.HandleFunc("/events", eventHandler) http.HandleFunc("/events", eventHandler)